use serde::{Deserialize, Serialize};
use crate::{
ZaiResult,
client::error::{ZaiError, codes},
};
fn empty_response(operation: &str) -> ZaiError {
ZaiError::ApiError {
code: codes::SDK_VALIDATION,
message: format!("{operation} response contained no documented fields"),
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssistantResponseContentPart {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_: Option<AssistantResponseContentType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AssistantResponseContentType {
#[serde(rename = "text")]
Text,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AssistantResponseContent {
Text(String),
Parts(Vec<AssistantResponseContentPart>),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssistantResponseAudio {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssistantResponseFunctionCall {
pub name: String,
pub arguments: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AssistantMcpCallType {
#[serde(rename = "mcp_list_tools")]
ListTools,
#[serde(rename = "mcp_call")]
Call,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AssistantMcpSchemaType {
#[serde(rename = "object")]
Object,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AssistantMcpInputSchema {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_: Option<AssistantMcpSchemaType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>,
#[serde(
rename = "additionalProperties",
skip_serializing_if = "Option::is_none"
)]
pub additional_properties: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AssistantMcpTool {
#[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::Map<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_schema: Option<AssistantMcpInputSchema>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AssistantMcpCall {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_: Option<AssistantMcpCallType>,
#[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<AssistantMcpTool>>,
#[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::Map<String, serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AssistantResponseToolCall {
#[serde(skip_serializing_if = "Option::is_none")]
pub function: Option<AssistantResponseFunctionCall>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mcp: Option<AssistantMcpCall>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AssistantResponseMessage {
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<AssistantResponseContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio: Option<AssistantResponseAudio>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<AssistantResponseToolCall>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AssistantInvokeChoice {
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<AssistantResponseMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub finish_reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssistantUsage {
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_tokens: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completion_tokens: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_tokens: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AssistantInvokeResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub choices: Option<Vec<AssistantInvokeChoice>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<AssistantUsage>,
}
impl AssistantInvokeResponse {
pub fn validate(&self) -> ZaiResult<()> {
if self.id.is_some()
|| self.request_id.is_some()
|| self.created.is_some()
|| self.model.is_some()
|| self.choices.is_some()
|| self.usage.is_some()
{
Ok(())
} else {
Err(empty_response("assistant invoke"))
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssistantTag {
#[serde(skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AssistantInfo {
pub assistant_id: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<AssistantTag>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub starter_prompts: Option<Vec<serde_json::Map<String, serde_json::Value>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AssistantListResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub success: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub msg: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Vec<AssistantInfo>>,
}
impl AssistantListResponse {
pub fn validate(&self) -> ZaiResult<()> {
if self.success.is_some()
|| self.code.is_some()
|| self.msg.is_some()
|| self.data.is_some()
{
Ok(())
} else {
Err(empty_response("assistant list"))
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssistantConversationUsage {
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_tokens: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completion_tokens: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_tokens: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssistantConversation {
pub id: String,
pub assistant_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub create_time: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub update_time: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<AssistantConversationUsage>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssistantConversationPage {
pub assistant_id: String,
pub conversation_list: Vec<AssistantConversation>,
pub has_more: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssistantConversationListResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub success: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub msg: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<AssistantConversationPage>,
}
impl AssistantConversationListResponse {
pub fn validate(&self) -> ZaiResult<()> {
if self.success.is_some()
|| self.code.is_some()
|| self.msg.is_some()
|| self.data.is_some()
{
Ok(())
} else {
Err(empty_response("assistant conversation list"))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn list_items_preserve_required_fields() {
let response: AssistantListResponse = serde_json::from_value(serde_json::json!({
"success": true,
"code": 200,
"msg": "ok",
"data": [{"assistant_id": "assistant-1", "name": "Helper"}]
}))
.unwrap();
assert_eq!(
response.data.as_ref().unwrap()[0].assistant_id,
"assistant-1"
);
assert!(response.validate().is_ok());
let missing_name = serde_json::from_value::<AssistantListResponse>(serde_json::json!({
"data": [{"assistant_id": "assistant-1"}]
}));
assert!(missing_name.is_err());
}
#[test]
fn empty_top_level_responses_violate_the_operations_contract() {
assert!(
serde_json::from_value::<AssistantInvokeResponse>(serde_json::json!({}))
.unwrap()
.validate()
.is_err()
);
assert!(
serde_json::from_value::<AssistantListResponse>(serde_json::json!({}))
.unwrap()
.validate()
.is_err()
);
assert!(
serde_json::from_value::<AssistantConversationListResponse>(serde_json::json!({}))
.unwrap()
.validate()
.is_err()
);
}
}