1use crate::api::base::Validate;
39use crate::models::functions::FunctionTool;
40use crate::{De, Ser};
41use serde::{self, Deserialize, Serialize};
42use std::collections::HashMap;
43
44#[derive(Debug, Clone, PartialEq, Eq, Ser, De)]
46#[serde(tag = "type", rename_all = "snake_case")]
47pub enum AssistantTool {
48 CodeInterpreter,
50 Retrieval,
52 Function {
54 function: FunctionTool,
56 },
57}
58
59impl AssistantTool {
60 #[must_use]
62 pub fn code_interpreter() -> Self {
63 Self::CodeInterpreter
64 }
65
66 #[must_use]
68 pub fn retrieval() -> Self {
69 Self::Retrieval
70 }
71
72 #[must_use]
74 pub fn function(function: FunctionTool) -> Self {
75 Self::Function { function }
76 }
77
78 #[must_use]
80 pub fn tool_type(&self) -> &'static str {
81 match self {
82 Self::CodeInterpreter => "code_interpreter",
83 Self::Retrieval => "retrieval",
84 Self::Function { .. } => "function",
85 }
86 }
87}
88
89#[derive(Debug, Clone, Ser, De)]
92pub struct Assistant {
93 pub id: String,
95 #[serde(default = "default_object_type")]
97 pub object: String,
98 pub created_at: i64,
100 pub name: Option<String>,
102 pub description: Option<String>,
104 pub model: String,
106 pub instructions: Option<String>,
108 #[serde(default)]
110 pub tools: Vec<AssistantTool>,
111 #[serde(default)]
113 pub file_ids: Vec<String>,
114 #[serde(default)]
116 pub metadata: HashMap<String, String>,
117}
118
119crate::impl_default_object_type!(default_object_type, "assistant");
120
121#[derive(Debug, Clone, Ser, De)]
123pub struct AssistantRequest {
124 pub model: String,
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub name: Option<String>,
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub description: Option<String>,
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub instructions: Option<String>,
135 #[serde(default, skip_serializing_if = "Vec::is_empty")]
137 pub tools: Vec<AssistantTool>,
138 #[serde(default, skip_serializing_if = "Vec::is_empty")]
140 pub file_ids: Vec<String>,
141 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
143 pub metadata: HashMap<String, String>,
144}
145
146impl AssistantRequest {
147 #[must_use]
149 pub fn builder() -> AssistantRequestBuilder {
150 AssistantRequestBuilder::new()
151 }
152
153 pub fn new(model: impl Into<String>) -> Self {
155 Self {
156 model: model.into(),
157 name: None,
158 description: None,
159 instructions: None,
160 tools: Vec::new(),
161 file_ids: Vec::new(),
162 metadata: HashMap::new(),
163 }
164 }
165
166 pub fn validate(&self) -> Result<(), String> {
168 self.validate_text_fields()?;
169 self.validate_collections()?;
170 self.validate_metadata()?;
171 Ok(())
172 }
173
174 fn validate_text_fields(&self) -> Result<(), String> {
176 self.validate_name()?;
177 self.validate_description()?;
178 self.validate_instructions()?;
179 Ok(())
180 }
181
182 fn validate_name(&self) -> Result<(), String> {
184 if let Some(name) = &self.name {
185 if name.len() > 256 {
186 return Err("Assistant name cannot exceed 256 characters".to_string());
187 }
188 }
189 Ok(())
190 }
191
192 fn validate_description(&self) -> Result<(), String> {
194 if let Some(description) = &self.description {
195 if description.len() > 512 {
196 return Err("Assistant description cannot exceed 512 characters".to_string());
197 }
198 }
199 Ok(())
200 }
201
202 fn validate_instructions(&self) -> Result<(), String> {
204 if let Some(instructions) = &self.instructions {
205 if instructions.len() > 32768 {
206 return Err("Assistant instructions cannot exceed 32768 characters".to_string());
207 }
208 }
209 Ok(())
210 }
211
212 fn validate_collections(&self) -> Result<(), String> {
214 self.validate_tools_count()?;
215 self.validate_file_ids_count()?;
216 Ok(())
217 }
218
219 fn validate_tools_count(&self) -> Result<(), String> {
221 if self.tools.len() > 128 {
222 return Err("Assistant cannot have more than 128 tools".to_string());
223 }
224 Ok(())
225 }
226
227 fn validate_file_ids_count(&self) -> Result<(), String> {
229 if self.file_ids.len() > 20 {
230 return Err("Assistant cannot have more than 20 file IDs".to_string());
231 }
232 Ok(())
233 }
234
235 fn validate_metadata(&self) -> Result<(), String> {
237 self.validate_metadata_count()?;
238 self.validate_metadata_entries()?;
239 Ok(())
240 }
241
242 fn validate_metadata_count(&self) -> Result<(), String> {
244 if self.metadata.len() > 16 {
245 return Err("Assistant cannot have more than 16 metadata pairs".to_string());
246 }
247 Ok(())
248 }
249
250 fn validate_metadata_entries(&self) -> Result<(), String> {
252 for (key, value) in &self.metadata {
253 if key.len() > 64 {
254 return Err("Metadata key cannot exceed 64 characters".to_string());
255 }
256 if value.len() > 512 {
257 return Err("Metadata value cannot exceed 512 characters".to_string());
258 }
259 }
260 Ok(())
261 }
262}
263
264impl Validate for AssistantRequest {
266 fn validate(&self) -> Result<(), String> {
267 self.validate()
268 }
269}
270
271#[derive(Debug, Clone, Default)]
273pub struct AssistantRequestBuilder {
274 model: Option<String>,
276 name: Option<String>,
278 description: Option<String>,
280 instructions: Option<String>,
282 tools: Vec<AssistantTool>,
284 file_ids: Vec<String>,
286 metadata: HashMap<String, String>,
288}
289
290impl AssistantRequestBuilder {
291 #[must_use]
293 pub fn new() -> Self {
294 Self::default()
295 }
296
297 pub fn model(mut self, model: impl Into<String>) -> Self {
299 self.model = Some(model.into());
300 self
301 }
302
303 pub fn name(mut self, name: impl Into<String>) -> Self {
305 self.name = Some(name.into());
306 self
307 }
308
309 pub fn description(mut self, description: impl Into<String>) -> Self {
311 self.description = Some(description.into());
312 self
313 }
314
315 pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
317 self.instructions = Some(instructions.into());
318 self
319 }
320
321 #[must_use]
323 pub fn tool(mut self, tool: AssistantTool) -> Self {
324 self.tools.push(tool);
325 self
326 }
327
328 #[must_use]
330 pub fn tools(mut self, tools: Vec<AssistantTool>) -> Self {
331 self.tools.extend(tools);
332 self
333 }
334
335 pub fn file_id(mut self, file_id: impl Into<String>) -> Self {
337 self.file_ids.push(file_id.into());
338 self
339 }
340
341 #[must_use]
343 pub fn file_ids(mut self, file_ids: Vec<String>) -> Self {
344 self.file_ids.extend(file_ids);
345 self
346 }
347
348 pub fn metadata_pair(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
350 self.metadata.insert(key.into(), value.into());
351 self
352 }
353
354 #[must_use]
356 pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
357 self.metadata = metadata;
358 self
359 }
360}
361
362crate::impl_builder_build! {
364 AssistantRequestBuilder => AssistantRequest {
365 required: [model: "Model is required"],
366 optional: [name, description, instructions, tools, file_ids, metadata],
367 validate: true
368 }
369}
370
371#[derive(Debug, Clone, Ser, De)]
373pub struct ListAssistantsResponse {
374 #[serde(default = "default_list_object")]
376 pub object: String,
377 pub data: Vec<Assistant>,
379 pub first_id: Option<String>,
381 pub last_id: Option<String>,
383 pub has_more: bool,
385}
386
387crate::impl_default_object_type!(default_list_object, "list");
388
389#[derive(Debug, Clone, Default, Ser, De)]
391pub struct ListAssistantsParams {
392 #[serde(skip_serializing_if = "Option::is_none")]
394 pub limit: Option<u32>,
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub order: Option<SortOrder>,
398 #[serde(skip_serializing_if = "Option::is_none")]
400 pub after: Option<String>,
401 #[serde(skip_serializing_if = "Option::is_none")]
403 pub before: Option<String>,
404}
405
406impl ListAssistantsParams {
407 #[must_use]
409 pub fn new() -> Self {
410 Self::default()
411 }
412
413 #[must_use]
415 pub fn limit(mut self, limit: u32) -> Self {
416 self.limit = Some(limit.clamp(1, 100));
417 self
418 }
419
420 #[must_use]
422 pub fn order(mut self, order: SortOrder) -> Self {
423 self.order = Some(order);
424 self
425 }
426
427 pub fn after(mut self, after: impl Into<String>) -> Self {
429 self.after = Some(after.into());
430 self
431 }
432
433 pub fn before(mut self, before: impl Into<String>) -> Self {
435 self.before = Some(before.into());
436 self
437 }
438
439 #[must_use]
441 pub fn to_query_params(&self) -> Vec<(String, String)> {
442 let mut params = Vec::new();
443 if let Some(limit) = self.limit {
444 params.push(("limit".to_string(), limit.to_string()));
445 }
446 if let Some(order) = &self.order {
447 let order_str = match order {
448 SortOrder::Asc => "asc",
449 SortOrder::Desc => "desc",
450 };
451 params.push(("order".to_string(), order_str.to_string()));
452 }
453 if let Some(after) = &self.after {
454 params.push(("after".to_string(), after.clone()));
455 }
456 if let Some(before) = &self.before {
457 params.push(("before".to_string(), before.clone()));
458 }
459 params
460 }
461}
462
463impl crate::api::common::ListQueryParams for ListAssistantsParams {
464 fn limit(&self) -> Option<u32> {
465 self.limit
466 }
467
468 fn order_str(&self) -> Option<&str> {
469 self.order.as_ref().map(|o| match o {
470 SortOrder::Asc => "asc",
471 SortOrder::Desc => "desc",
472 })
473 }
474
475 fn after(&self) -> Option<&String> {
476 self.after.as_ref()
477 }
478
479 fn before(&self) -> Option<&String> {
480 self.before.as_ref()
481 }
482}
483
484#[derive(Debug, Clone, PartialEq, Eq, Ser, De)]
486#[serde(rename_all = "lowercase")]
487#[derive(Default)]
488pub enum SortOrder {
489 Asc,
491 #[default]
493 Desc,
494}
495
496#[derive(Debug, Clone, Ser, De)]
498pub struct DeletionStatus {
499 pub id: String,
501 #[serde(default = "default_deletion_object")]
503 pub object: String,
504 pub deleted: bool,
506}
507
508crate::impl_default_object_type!(default_deletion_object, "assistant.deleted");
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513
514 #[test]
515 fn test_assistant_tool_creation() {
516 let code_interpreter = AssistantTool::code_interpreter();
517 assert_eq!(code_interpreter.tool_type(), "code_interpreter");
518
519 let retrieval = AssistantTool::retrieval();
520 assert_eq!(retrieval.tool_type(), "retrieval");
521 }
522
523 #[test]
524 fn test_assistant_request_builder() {
525 let request = AssistantRequest::builder()
526 .model("gpt-4")
527 .name("Test Assistant")
528 .description("A test assistant")
529 .instructions("You are a helpful assistant")
530 .tool(AssistantTool::code_interpreter())
531 .file_id("file-123")
532 .metadata_pair("purpose", "testing")
533 .build()
534 .unwrap();
535
536 assert_eq!(request.model, "gpt-4");
537 assert_eq!(request.name, Some("Test Assistant".to_string()));
538 assert_eq!(request.tools.len(), 1);
539 assert_eq!(request.file_ids.len(), 1);
540 assert_eq!(request.metadata.len(), 1);
541 }
542
543 #[test]
544 fn test_assistant_request_validation() {
545 let long_name = "a".repeat(257);
547 let request = AssistantRequest::builder()
548 .model("gpt-4")
549 .name(long_name)
550 .build();
551 assert!(request.is_err());
552
553 let request = AssistantRequest::builder()
555 .model("gpt-4")
556 .name("Valid Name")
557 .build();
558 assert!(request.is_ok());
559 }
560
561 #[test]
562 fn test_list_params_limit_clamping() {
563 let params = ListAssistantsParams::new().limit(150);
564 assert_eq!(params.limit, Some(100));
565
566 let params = ListAssistantsParams::new().limit(0);
567 assert_eq!(params.limit, Some(1));
568 }
569
570 #[test]
571 fn test_sort_order_default() {
572 let order = SortOrder::default();
573 assert_eq!(order, SortOrder::Desc);
574 }
575}