use serde::{Deserialize, Deserializer, Serialize};
use validator::Validate;
fn de_opt_string_from_number_or_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let v = serde_json::Value::deserialize(deserializer)?;
match v {
serde_json::Value::Null => Ok(None),
serde_json::Value::String(s) => Ok(Some(s)),
serde_json::Value::Number(n) => Ok(Some(n.to_string())),
other => Err(serde::de::Error::custom(format!(
"expected string or number, got {}",
other
))),
}
}
#[derive(Clone, Serialize, Deserialize, Validate, Default)]
#[serde(default)]
pub struct ChatCompletionResponse {
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "de_opt_string_from_number_or_string"
)]
pub id: Option<String>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "de_opt_string_from_number_or_string"
)]
pub request_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub choices: Option<Vec<Choice>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub video_result: Option<Vec<VideoResultItem>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub web_search: Option<Vec<WebSearchInfo>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_filter: Option<Vec<ContentFilterInfo>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub task_status: Option<TaskStatus>,
}
impl std::fmt::Debug for ChatCompletionResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match serde_json::to_string_pretty(self) {
Ok(s) => f.write_str(&s),
Err(_) => f.debug_struct("ChatCompletionResponse").finish(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TaskStatus {
#[serde(rename = "PROCESSING", alias = "processing")]
Processing,
#[serde(rename = "SUCCESS", alias = "success")]
Success,
#[serde(rename = "FAIL", alias = "fail")]
Fail,
}
impl TaskStatus {
pub fn as_str(&self) -> &'static str {
match self {
TaskStatus::Processing => "PROCESSING",
TaskStatus::Success => "SUCCESS",
TaskStatus::Fail => "FAIL",
}
}
}
impl std::fmt::Display for TaskStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct Choice {
pub index: i32,
pub message: Message,
#[serde(skip_serializing_if = "Option::is_none")]
pub finish_reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct Message {
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio: Option<AudioContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCallMessage>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ToolCallMessage {
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "de_opt_string_from_number_or_string"
)]
pub id: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub function: Option<ToolFunction>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mcp: Option<MCPMessage>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ToolFunction {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct MCPMessage {
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "de_opt_string_from_number_or_string"
)]
pub id: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_: Option<MCPCallType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub server_label: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<MCPTool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MCPCallType {
McpListTools,
McpCall,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct MCPTool {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_schema: Option<MCPInputSchema>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct MCPInputSchema {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_: Option<MCPInputType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_properties: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MCPInputType {
Object,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct AudioContent {
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "de_opt_string_from_number_or_string"
)]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "de_opt_string_from_number_or_string"
)]
pub expires_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct Usage {
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completion_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_tokens_details: Option<PromptTokensDetails>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct PromptTokensDetails {
#[serde(skip_serializing_if = "Option::is_none")]
pub cached_tokens: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct WebSearchInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[validate(url)]
pub link: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub media: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub publish_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub refer: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct VideoResultItem {
#[serde(skip_serializing_if = "Option::is_none")]
#[validate(url)]
pub url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[validate(url)]
pub cover_image_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ContentFilterInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[validate(range(min = 0, max = 3))]
pub level: Option<i32>,
}
impl ChatCompletionResponse {
pub fn id(&self) -> Option<&str> {
self.id.as_deref()
}
pub fn request_id(&self) -> Option<&str> {
self.request_id.as_deref()
}
pub fn created(&self) -> Option<u64> {
self.created
}
pub fn model(&self) -> Option<&str> {
self.model.as_deref()
}
pub fn choices(&self) -> Option<&[Choice]> {
self.choices.as_deref()
}
pub fn usage(&self) -> Option<&Usage> {
self.usage.as_ref()
}
pub fn video_result(&self) -> Option<&[VideoResultItem]> {
self.video_result.as_deref()
}
pub fn web_search(&self) -> Option<&[WebSearchInfo]> {
self.web_search.as_deref()
}
pub fn content_filter(&self) -> Option<&[ContentFilterInfo]> {
self.content_filter.as_deref()
}
pub fn task_status(&self) -> Option<&TaskStatus> {
self.task_status.as_ref()
}
}
impl Choice {
pub fn index(&self) -> i32 {
self.index
}
pub fn message(&self) -> &Message {
&self.message
}
pub fn finish_reason(&self) -> Option<&str> {
self.finish_reason.as_deref()
}
}
impl Message {
pub fn role(&self) -> Option<&str> {
self.role.as_deref()
}
pub fn content(&self) -> Option<&serde_json::Value> {
self.content.as_ref()
}
pub fn reasoning_content(&self) -> Option<&str> {
self.reasoning_content.as_deref()
}
pub fn audio(&self) -> Option<&AudioContent> {
self.audio.as_ref()
}
pub fn tool_calls(&self) -> Option<&[ToolCallMessage]> {
self.tool_calls.as_deref()
}
}
impl ToolCallMessage {
pub fn id(&self) -> Option<&str> {
self.id.as_deref()
}
pub fn type_(&self) -> Option<&str> {
self.type_.as_deref()
}
pub fn function(&self) -> Option<&ToolFunction> {
self.function.as_ref()
}
pub fn mcp(&self) -> Option<&MCPMessage> {
self.mcp.as_ref()
}
}
impl ToolFunction {
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn arguments(&self) -> Option<&str> {
self.arguments.as_deref()
}
}
impl MCPMessage {
pub fn id(&self) -> Option<&str> {
self.id.as_deref()
}
pub fn type_(&self) -> Option<&MCPCallType> {
self.type_.as_ref()
}
pub fn server_label(&self) -> Option<&str> {
self.server_label.as_deref()
}
pub fn error(&self) -> Option<&str> {
self.error.as_deref()
}
pub fn tools(&self) -> Option<&[MCPTool]> {
self.tools.as_deref()
}
pub fn arguments(&self) -> Option<&str> {
self.arguments.as_deref()
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn output(&self) -> Option<&serde_json::Value> {
self.output.as_ref()
}
}
impl MCPTool {
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn annotations(&self) -> Option<&serde_json::Value> {
self.annotations.as_ref()
}
pub fn input_schema(&self) -> Option<&MCPInputSchema> {
self.input_schema.as_ref()
}
}
impl MCPInputSchema {
pub fn type_(&self) -> Option<&MCPInputType> {
self.type_.as_ref()
}
pub fn properties(&self) -> Option<&serde_json::Value> {
self.properties.as_ref()
}
pub fn required(&self) -> Option<&[String]> {
self.required.as_deref()
}
pub fn additional_properties(&self) -> Option<bool> {
self.additional_properties
}
}
impl AudioContent {
pub fn id(&self) -> Option<&str> {
self.id.as_deref()
}
pub fn data(&self) -> Option<&str> {
self.data.as_deref()
}
pub fn expires_at(&self) -> Option<&str> {
self.expires_at.as_deref()
}
}
impl Usage {
pub fn prompt_tokens(&self) -> Option<u32> {
self.prompt_tokens
}
pub fn completion_tokens(&self) -> Option<u32> {
self.completion_tokens
}
pub fn total_tokens(&self) -> Option<u32> {
self.total_tokens
}
pub fn prompt_tokens_details(&self) -> Option<&PromptTokensDetails> {
self.prompt_tokens_details.as_ref()
}
}
impl PromptTokensDetails {
pub fn cached_tokens(&self) -> Option<u32> {
self.cached_tokens
}
}
impl WebSearchInfo {
pub fn icon(&self) -> Option<&str> {
self.icon.as_deref()
}
pub fn title(&self) -> Option<&str> {
self.title.as_deref()
}
pub fn link(&self) -> Option<&str> {
self.link.as_deref()
}
pub fn media(&self) -> Option<&str> {
self.media.as_deref()
}
pub fn publish_date(&self) -> Option<&str> {
self.publish_date.as_deref()
}
pub fn content(&self) -> Option<&str> {
self.content.as_deref()
}
pub fn refer(&self) -> Option<&str> {
self.refer.as_deref()
}
}
impl VideoResultItem {
pub fn url(&self) -> Option<&str> {
self.url.as_deref()
}
pub fn cover_image_url(&self) -> Option<&str> {
self.cover_image_url.as_deref()
}
}
impl ContentFilterInfo {
pub fn role(&self) -> Option<&str> {
self.role.as_deref()
}
pub fn level(&self) -> Option<i32> {
self.level
}
}