1use serde::{Deserialize, Serialize};
4
5use crate::{
6 ZaiResult,
7 client::error::{ZaiError, codes},
8};
9
10fn empty_response(operation: &str) -> ZaiError {
11 ZaiError::ApiError {
12 code: codes::SDK_VALIDATION,
13 message: format!("{operation} response contained no documented fields"),
14 }
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct AssistantResponseContentPart {
20 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
22 pub type_: Option<AssistantResponseContentType>,
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub text: Option<String>,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30pub enum AssistantResponseContentType {
31 #[serde(rename = "text")]
33 Text,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(untagged)]
39pub enum AssistantResponseContent {
40 Text(String),
42 Parts(Vec<AssistantResponseContentPart>),
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48pub struct AssistantResponseAudio {
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub id: Option<String>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub data: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub expires_at: Option<String>,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct AssistantResponseFunctionCall {
63 pub name: String,
65 pub arguments: String,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
71pub enum AssistantMcpCallType {
72 #[serde(rename = "mcp_list_tools")]
74 ListTools,
75 #[serde(rename = "mcp_call")]
77 Call,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82pub enum AssistantMcpSchemaType {
83 #[serde(rename = "object")]
85 Object,
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90pub struct AssistantMcpInputSchema {
91 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
93 pub type_: Option<AssistantMcpSchemaType>,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub properties: Option<serde_json::Map<String, serde_json::Value>>,
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub required: Option<Vec<String>>,
100 #[serde(
102 rename = "additionalProperties",
103 skip_serializing_if = "Option::is_none"
104 )]
105 pub additional_properties: Option<bool>,
106}
107
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
110pub struct AssistantMcpTool {
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub name: Option<String>,
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub description: Option<String>,
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub annotations: Option<serde_json::Map<String, serde_json::Value>>,
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub input_schema: Option<AssistantMcpInputSchema>,
123}
124
125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127pub struct AssistantMcpCall {
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub id: Option<String>,
131 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
133 pub type_: Option<AssistantMcpCallType>,
134 #[serde(skip_serializing_if = "Option::is_none")]
136 pub server_label: Option<String>,
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub error: Option<String>,
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub tools: Option<Vec<AssistantMcpTool>>,
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub arguments: Option<String>,
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub name: Option<String>,
149 #[serde(skip_serializing_if = "Option::is_none")]
151 pub output: Option<serde_json::Map<String, serde_json::Value>>,
152}
153
154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156pub struct AssistantResponseToolCall {
157 #[serde(skip_serializing_if = "Option::is_none")]
159 pub function: Option<AssistantResponseFunctionCall>,
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub mcp: Option<AssistantMcpCall>,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub id: Option<String>,
166 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
168 pub type_: Option<String>,
169}
170
171#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173pub struct AssistantResponseMessage {
174 #[serde(skip_serializing_if = "Option::is_none")]
176 pub role: Option<String>,
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub content: Option<AssistantResponseContent>,
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub reasoning_content: Option<String>,
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub audio: Option<AssistantResponseAudio>,
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub tool_calls: Option<Vec<AssistantResponseToolCall>>,
189}
190
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct AssistantInvokeChoice {
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub index: Option<i64>,
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub message: Option<AssistantResponseMessage>,
200 #[serde(skip_serializing_if = "Option::is_none")]
202 pub finish_reason: Option<String>,
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
207pub struct AssistantUsage {
208 #[serde(skip_serializing_if = "Option::is_none")]
210 pub prompt_tokens: Option<i64>,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub completion_tokens: Option<i64>,
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub total_tokens: Option<i64>,
217}
218
219#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
221pub struct AssistantInvokeResponse {
222 #[serde(skip_serializing_if = "Option::is_none")]
224 pub id: Option<String>,
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub request_id: Option<String>,
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub created: Option<i64>,
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub model: Option<String>,
234 #[serde(skip_serializing_if = "Option::is_none")]
236 pub choices: Option<Vec<AssistantInvokeChoice>>,
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub usage: Option<AssistantUsage>,
240}
241
242impl AssistantInvokeResponse {
243 pub fn validate(&self) -> ZaiResult<()> {
245 if self.id.is_some()
246 || self.request_id.is_some()
247 || self.created.is_some()
248 || self.model.is_some()
249 || self.choices.is_some()
250 || self.usage.is_some()
251 {
252 Ok(())
253 } else {
254 Err(empty_response("assistant invoke"))
255 }
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
261pub struct AssistantTag {
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub key: Option<String>,
265 #[serde(skip_serializing_if = "Option::is_none")]
267 pub label: Option<String>,
268}
269
270#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
272pub struct AssistantInfo {
273 pub assistant_id: String,
275 pub name: String,
277 #[serde(skip_serializing_if = "Option::is_none")]
279 pub avatar: Option<String>,
280 #[serde(skip_serializing_if = "Option::is_none")]
282 pub description: Option<String>,
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub tools: Option<Vec<String>>,
286 #[serde(skip_serializing_if = "Option::is_none")]
288 pub tags: Option<Vec<AssistantTag>>,
289 #[serde(skip_serializing_if = "Option::is_none")]
291 pub status: Option<String>,
292 #[serde(skip_serializing_if = "Option::is_none")]
294 pub starter_prompts: Option<Vec<serde_json::Map<String, serde_json::Value>>>,
295 #[serde(skip_serializing_if = "Option::is_none")]
297 pub created_at: Option<String>,
298 #[serde(skip_serializing_if = "Option::is_none")]
300 pub updated_at: Option<String>,
301}
302
303#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
305pub struct AssistantListResponse {
306 #[serde(skip_serializing_if = "Option::is_none")]
308 pub success: Option<bool>,
309 #[serde(skip_serializing_if = "Option::is_none")]
311 pub code: Option<i64>,
312 #[serde(skip_serializing_if = "Option::is_none")]
314 pub msg: Option<String>,
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub data: Option<Vec<AssistantInfo>>,
318}
319
320impl AssistantListResponse {
321 pub fn validate(&self) -> ZaiResult<()> {
323 if self.success.is_some()
324 || self.code.is_some()
325 || self.msg.is_some()
326 || self.data.is_some()
327 {
328 Ok(())
329 } else {
330 Err(empty_response("assistant list"))
331 }
332 }
333}
334
335#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
337pub struct AssistantConversationUsage {
338 #[serde(skip_serializing_if = "Option::is_none")]
340 pub prompt_tokens: Option<i64>,
341 #[serde(skip_serializing_if = "Option::is_none")]
343 pub completion_tokens: Option<i64>,
344 #[serde(skip_serializing_if = "Option::is_none")]
346 pub total_tokens: Option<i64>,
347}
348
349#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
351pub struct AssistantConversation {
352 pub id: String,
354 pub assistant_id: String,
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub create_time: Option<String>,
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub update_time: Option<String>,
362 #[serde(skip_serializing_if = "Option::is_none")]
364 pub usage: Option<AssistantConversationUsage>,
365}
366
367#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
369pub struct AssistantConversationPage {
370 pub assistant_id: String,
372 pub conversation_list: Vec<AssistantConversation>,
374 pub has_more: bool,
376}
377
378#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
380pub struct AssistantConversationListResponse {
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub success: Option<bool>,
384 #[serde(skip_serializing_if = "Option::is_none")]
386 pub code: Option<i64>,
387 #[serde(skip_serializing_if = "Option::is_none")]
389 pub msg: Option<String>,
390 #[serde(skip_serializing_if = "Option::is_none")]
392 pub data: Option<AssistantConversationPage>,
393}
394
395impl AssistantConversationListResponse {
396 pub fn validate(&self) -> ZaiResult<()> {
398 if self.success.is_some()
399 || self.code.is_some()
400 || self.msg.is_some()
401 || self.data.is_some()
402 {
403 Ok(())
404 } else {
405 Err(empty_response("assistant conversation list"))
406 }
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 #[test]
415 fn list_items_preserve_required_fields() {
416 let response: AssistantListResponse = serde_json::from_value(serde_json::json!({
417 "success": true,
418 "code": 200,
419 "msg": "ok",
420 "data": [{"assistant_id": "assistant-1", "name": "Helper"}]
421 }))
422 .unwrap();
423 assert_eq!(
424 response.data.as_ref().unwrap()[0].assistant_id,
425 "assistant-1"
426 );
427 assert!(response.validate().is_ok());
428
429 let missing_name = serde_json::from_value::<AssistantListResponse>(serde_json::json!({
430 "data": [{"assistant_id": "assistant-1"}]
431 }));
432 assert!(missing_name.is_err());
433 }
434
435 #[test]
436 fn empty_top_level_responses_violate_the_operations_contract() {
437 assert!(
438 serde_json::from_value::<AssistantInvokeResponse>(serde_json::json!({}))
439 .unwrap()
440 .validate()
441 .is_err()
442 );
443 assert!(
444 serde_json::from_value::<AssistantListResponse>(serde_json::json!({}))
445 .unwrap()
446 .validate()
447 .is_err()
448 );
449 assert!(
450 serde_json::from_value::<AssistantConversationListResponse>(serde_json::json!({}))
451 .unwrap()
452 .validate()
453 .is_err()
454 );
455 }
456}