Skip to main content

openai_oxide/types/
beta.rs

1// Beta API types — Assistants, Threads, Runs, Vector Stores
2
3use serde::{Deserialize, Serialize};
4
5use super::common::{Role, SortOrder};
6
7/// Status of a thread run.
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10#[non_exhaustive]
11pub enum RunStatus {
12    Queued,
13    InProgress,
14    RequiresAction,
15    Cancelling,
16    Cancelled,
17    Failed,
18    Completed,
19    Incomplete,
20    Expired,
21}
22
23/// Status of a vector store.
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "snake_case")]
26#[non_exhaustive]
27pub enum VectorStoreStatus {
28    Expired,
29    InProgress,
30    Completed,
31}
32
33// ── Tool types ──
34
35/// A tool available to an assistant or run.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37#[serde(tag = "type")]
38#[non_exhaustive]
39pub enum BetaTool {
40    /// Code interpreter tool.
41    #[serde(rename = "code_interpreter")]
42    CodeInterpreter,
43    /// File search tool with optional configuration.
44    #[serde(rename = "file_search")]
45    FileSearch {
46        #[serde(skip_serializing_if = "Option::is_none")]
47        file_search: Option<FileSearchConfig>,
48    },
49    /// Function tool.
50    #[serde(rename = "function")]
51    Function { function: BetaFunctionDef },
52}
53
54/// Configuration for the file search tool.
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct FileSearchConfig {
57    /// Maximum number of results (1–50).
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub max_num_results: Option<i64>,
60    /// Ranking options.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub ranking_options: Option<FileSearchRankingOptions>,
63}
64
65/// Ranking options for file search.
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct FileSearchRankingOptions {
68    /// Score threshold (0.0–1.0).
69    pub score_threshold: f64,
70    /// Ranker to use: "auto" or "default_2024_08_21".
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub ranker: Option<String>,
73}
74
75/// Function definition within a beta tool.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct BetaFunctionDef {
78    pub name: String,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub description: Option<String>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub parameters: Option<serde_json::Value>,
83}
84
85/// An annotation on message text (file citation or file path).
86#[derive(Debug, Clone, Deserialize)]
87pub struct MessageAnnotation {
88    /// Annotation type: "file_citation" or "file_path".
89    #[serde(rename = "type")]
90    pub type_: String,
91    /// The text in the message content being annotated.
92    #[serde(default)]
93    pub text: Option<String>,
94    /// Start index of the annotation in the text.
95    #[serde(default)]
96    pub start_index: Option<i64>,
97    /// End index of the annotation in the text.
98    #[serde(default)]
99    pub end_index: Option<i64>,
100    /// File citation details (for file_citation type).
101    #[serde(default)]
102    pub file_citation: Option<FileCitation>,
103    /// File path details (for file_path type).
104    #[serde(default)]
105    pub file_path: Option<FilePath>,
106}
107
108/// File citation in an annotation.
109#[derive(Debug, Clone, Deserialize)]
110pub struct FileCitation {
111    pub file_id: String,
112    #[serde(default)]
113    pub quote: Option<String>,
114}
115
116/// File path in an annotation.
117#[derive(Debug, Clone, Deserialize)]
118pub struct FilePath {
119    pub file_id: String,
120}
121
122// ── Assistants ──
123
124/// Request body for creating an assistant.
125#[derive(Debug, Clone, Serialize)]
126pub struct AssistantCreateRequest {
127    pub model: String,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub name: Option<String>,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub description: Option<String>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub instructions: Option<String>,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub tools: Option<Vec<BetaTool>>,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub metadata: Option<std::collections::HashMap<String, String>>,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub temperature: Option<f64>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub top_p: Option<f64>,
142}
143
144impl AssistantCreateRequest {
145    pub fn new(model: impl Into<String>) -> Self {
146        Self {
147            model: model.into(),
148            name: None,
149            description: None,
150            instructions: None,
151            tools: None,
152            metadata: None,
153            temperature: None,
154            top_p: None,
155        }
156    }
157}
158
159/// An assistant object.
160#[derive(Debug, Clone, Deserialize)]
161pub struct Assistant {
162    pub id: String,
163    pub object: String,
164    pub created_at: i64,
165    pub model: String,
166    #[serde(default)]
167    pub name: Option<String>,
168    #[serde(default)]
169    pub description: Option<String>,
170    #[serde(default)]
171    pub instructions: Option<String>,
172    #[serde(default)]
173    pub tools: Vec<BetaTool>,
174    #[serde(default)]
175    pub metadata: Option<std::collections::HashMap<String, String>>,
176    #[serde(default)]
177    pub temperature: Option<f64>,
178    #[serde(default)]
179    pub top_p: Option<f64>,
180}
181
182/// List of assistants.
183#[derive(Debug, Clone, Deserialize)]
184pub struct AssistantList {
185    pub object: String,
186    pub data: Vec<Assistant>,
187    /// Whether there are more results available.
188    #[serde(default)]
189    pub has_more: Option<bool>,
190    /// ID of the first object in the list.
191    #[serde(default)]
192    pub first_id: Option<String>,
193    /// ID of the last object in the list.
194    #[serde(default)]
195    pub last_id: Option<String>,
196}
197
198/// Deleted assistant response.
199#[derive(Debug, Clone, Deserialize)]
200pub struct AssistantDeleted {
201    pub id: String,
202    pub deleted: bool,
203    pub object: String,
204}
205
206// ── Threads ──
207
208/// Request body for creating a thread.
209#[derive(Debug, Clone, Default, Serialize)]
210pub struct ThreadCreateRequest {
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub messages: Option<Vec<ThreadMessage>>,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub metadata: Option<std::collections::HashMap<String, String>>,
215}
216
217/// A message in a thread creation request.
218#[derive(Debug, Clone, Serialize)]
219pub struct ThreadMessage {
220    pub role: Role,
221    pub content: String,
222}
223
224/// A thread object.
225#[derive(Debug, Clone, Deserialize)]
226pub struct Thread {
227    pub id: String,
228    pub object: String,
229    pub created_at: i64,
230    #[serde(default)]
231    pub metadata: Option<std::collections::HashMap<String, String>>,
232}
233
234/// Deleted thread response.
235#[derive(Debug, Clone, Deserialize)]
236pub struct ThreadDeleted {
237    pub id: String,
238    pub deleted: bool,
239    pub object: String,
240}
241
242/// A message within a thread.
243#[derive(Debug, Clone, Deserialize)]
244pub struct Message {
245    pub id: String,
246    pub object: String,
247    pub created_at: i64,
248    pub thread_id: String,
249    pub role: Role,
250    pub content: Vec<MessageContent>,
251    #[serde(default)]
252    pub assistant_id: Option<String>,
253    #[serde(default)]
254    pub run_id: Option<String>,
255    #[serde(default)]
256    pub metadata: Option<std::collections::HashMap<String, String>>,
257}
258
259/// Content block in a message.
260#[derive(Debug, Clone, Deserialize)]
261pub struct MessageContent {
262    #[serde(rename = "type")]
263    pub type_: String,
264    #[serde(default)]
265    pub text: Option<MessageText>,
266}
267
268/// Text content in a message.
269#[derive(Debug, Clone, Deserialize)]
270pub struct MessageText {
271    pub value: String,
272    #[serde(default)]
273    pub annotations: Vec<MessageAnnotation>,
274}
275
276/// Request body for creating a message.
277#[derive(Debug, Clone, Serialize)]
278pub struct MessageCreateRequest {
279    pub role: Role,
280    pub content: String,
281}
282
283/// List of messages.
284#[derive(Debug, Clone, Deserialize)]
285pub struct MessageList {
286    pub object: String,
287    pub data: Vec<Message>,
288    /// Whether there are more results available.
289    #[serde(default)]
290    pub has_more: Option<bool>,
291    /// ID of the first object in the list.
292    #[serde(default)]
293    pub first_id: Option<String>,
294    /// ID of the last object in the list.
295    #[serde(default)]
296    pub last_id: Option<String>,
297}
298
299// ── Runs ──
300
301/// Request body for creating a run.
302#[derive(Debug, Clone, Serialize)]
303pub struct RunCreateRequest {
304    pub assistant_id: String,
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub instructions: Option<String>,
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub tools: Option<Vec<BetaTool>>,
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub metadata: Option<std::collections::HashMap<String, String>>,
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub model: Option<String>,
313}
314
315impl RunCreateRequest {
316    pub fn new(assistant_id: impl Into<String>) -> Self {
317        Self {
318            assistant_id: assistant_id.into(),
319            instructions: None,
320            tools: None,
321            metadata: None,
322            model: None,
323        }
324    }
325}
326
327/// A run object.
328#[derive(Debug, Clone, Deserialize)]
329pub struct Run {
330    pub id: String,
331    pub object: String,
332    pub created_at: i64,
333    pub thread_id: String,
334    pub assistant_id: String,
335    pub status: RunStatus,
336    #[serde(default)]
337    pub model: Option<String>,
338    #[serde(default)]
339    pub instructions: Option<String>,
340    #[serde(default)]
341    pub tools: Vec<BetaTool>,
342    #[serde(default)]
343    pub started_at: Option<i64>,
344    #[serde(default)]
345    pub completed_at: Option<i64>,
346    #[serde(default)]
347    pub cancelled_at: Option<i64>,
348    #[serde(default)]
349    pub failed_at: Option<i64>,
350    #[serde(default)]
351    pub metadata: Option<std::collections::HashMap<String, String>>,
352}
353
354/// Submit tool outputs request.
355#[derive(Debug, Clone, Serialize)]
356pub struct SubmitToolOutputsRequest {
357    pub tool_outputs: Vec<ToolOutput>,
358}
359
360/// A single tool output.
361#[derive(Debug, Clone, Serialize)]
362pub struct ToolOutput {
363    pub tool_call_id: String,
364    pub output: String,
365}
366
367// ── Vector Stores ──
368
369/// Request body for creating a vector store.
370#[derive(Debug, Clone, Default, Serialize)]
371pub struct VectorStoreCreateRequest {
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub name: Option<String>,
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub file_ids: Option<Vec<String>>,
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub metadata: Option<std::collections::HashMap<String, String>>,
378}
379
380/// A vector store object.
381#[derive(Debug, Clone, Deserialize)]
382pub struct VectorStore {
383    pub id: String,
384    pub object: String,
385    pub created_at: i64,
386    #[serde(default)]
387    pub name: Option<String>,
388    pub status: VectorStoreStatus,
389    #[serde(default)]
390    pub file_counts: Option<VectorStoreFileCounts>,
391    #[serde(default)]
392    pub metadata: Option<std::collections::HashMap<String, String>>,
393}
394
395/// File counts for a vector store.
396#[derive(Debug, Clone, Deserialize)]
397pub struct VectorStoreFileCounts {
398    pub in_progress: i64,
399    pub completed: i64,
400    pub failed: i64,
401    pub cancelled: i64,
402    pub total: i64,
403}
404
405/// List of vector stores.
406#[derive(Debug, Clone, Deserialize)]
407pub struct VectorStoreList {
408    pub object: String,
409    pub data: Vec<VectorStore>,
410    /// Whether there are more results available.
411    #[serde(default)]
412    pub has_more: Option<bool>,
413    /// ID of the first object in the list.
414    #[serde(default)]
415    pub first_id: Option<String>,
416    /// ID of the last object in the list.
417    #[serde(default)]
418    pub last_id: Option<String>,
419}
420
421/// Deleted vector store response.
422#[derive(Debug, Clone, Deserialize)]
423pub struct VectorStoreDeleted {
424    pub id: String,
425    pub deleted: bool,
426    pub object: String,
427}
428
429/// Parameters for listing assistants with pagination.
430#[derive(Debug, Clone, Default)]
431pub struct AssistantListParams {
432    /// Cursor for pagination — fetch results after this assistant ID.
433    pub after: Option<String>,
434    /// Cursor for backward pagination — fetch results before this assistant ID.
435    pub before: Option<String>,
436    /// Maximum number of results per page (1–100).
437    pub limit: Option<i64>,
438    /// Sort order by `created_at`.
439    pub order: Option<SortOrder>,
440}
441
442impl AssistantListParams {
443    pub fn new() -> Self {
444        Self::default()
445    }
446
447    pub fn after(mut self, after: impl Into<String>) -> Self {
448        self.after = Some(after.into());
449        self
450    }
451
452    pub fn before(mut self, before: impl Into<String>) -> Self {
453        self.before = Some(before.into());
454        self
455    }
456
457    pub fn limit(mut self, limit: i64) -> Self {
458        self.limit = Some(limit);
459        self
460    }
461
462    pub fn order(mut self, order: SortOrder) -> Self {
463        self.order = Some(order);
464        self
465    }
466
467    /// Convert to query parameter pairs for the HTTP request.
468    pub fn to_query(&self) -> Vec<(String, String)> {
469        let mut q = Vec::new();
470        if let Some(ref after) = self.after {
471            q.push(("after".into(), after.clone()));
472        }
473        if let Some(ref before) = self.before {
474            q.push(("before".into(), before.clone()));
475        }
476        if let Some(limit) = self.limit {
477            q.push(("limit".into(), limit.to_string()));
478        }
479        if let Some(ref order) = self.order {
480            q.push((
481                "order".into(),
482                serde_json::to_value(order)
483                    .unwrap()
484                    .as_str()
485                    .unwrap()
486                    .to_string(),
487            ));
488        }
489        q
490    }
491}
492
493/// Parameters for listing messages with pagination.
494#[derive(Debug, Clone, Default)]
495pub struct MessageListParams {
496    /// Cursor for pagination — fetch results after this message ID.
497    pub after: Option<String>,
498    /// Cursor for backward pagination — fetch results before this message ID.
499    pub before: Option<String>,
500    /// Maximum number of results per page (1–100).
501    pub limit: Option<i64>,
502    /// Sort order by `created_at`.
503    pub order: Option<SortOrder>,
504    /// Filter by run ID.
505    pub run_id: Option<String>,
506}
507
508impl MessageListParams {
509    pub fn new() -> Self {
510        Self::default()
511    }
512
513    pub fn after(mut self, after: impl Into<String>) -> Self {
514        self.after = Some(after.into());
515        self
516    }
517
518    pub fn before(mut self, before: impl Into<String>) -> Self {
519        self.before = Some(before.into());
520        self
521    }
522
523    pub fn limit(mut self, limit: i64) -> Self {
524        self.limit = Some(limit);
525        self
526    }
527
528    pub fn order(mut self, order: SortOrder) -> Self {
529        self.order = Some(order);
530        self
531    }
532
533    pub fn run_id(mut self, run_id: impl Into<String>) -> Self {
534        self.run_id = Some(run_id.into());
535        self
536    }
537
538    /// Convert to query parameter pairs for the HTTP request.
539    pub fn to_query(&self) -> Vec<(String, String)> {
540        let mut q = Vec::new();
541        if let Some(ref after) = self.after {
542            q.push(("after".into(), after.clone()));
543        }
544        if let Some(ref before) = self.before {
545            q.push(("before".into(), before.clone()));
546        }
547        if let Some(limit) = self.limit {
548            q.push(("limit".into(), limit.to_string()));
549        }
550        if let Some(ref order) = self.order {
551            q.push((
552                "order".into(),
553                serde_json::to_value(order)
554                    .unwrap()
555                    .as_str()
556                    .unwrap()
557                    .to_string(),
558            ));
559        }
560        if let Some(ref run_id) = self.run_id {
561            q.push(("run_id".into(), run_id.clone()));
562        }
563        q
564    }
565}
566
567/// Parameters for listing vector stores with pagination.
568#[derive(Debug, Clone, Default)]
569pub struct VectorStoreListParams {
570    /// Cursor for pagination — fetch results after this vector store ID.
571    pub after: Option<String>,
572    /// Cursor for backward pagination — fetch results before this vector store ID.
573    pub before: Option<String>,
574    /// Maximum number of results per page (1–100).
575    pub limit: Option<i64>,
576    /// Sort order by `created_at`.
577    pub order: Option<SortOrder>,
578}
579
580impl VectorStoreListParams {
581    pub fn new() -> Self {
582        Self::default()
583    }
584
585    pub fn after(mut self, after: impl Into<String>) -> Self {
586        self.after = Some(after.into());
587        self
588    }
589
590    pub fn before(mut self, before: impl Into<String>) -> Self {
591        self.before = Some(before.into());
592        self
593    }
594
595    pub fn limit(mut self, limit: i64) -> Self {
596        self.limit = Some(limit);
597        self
598    }
599
600    pub fn order(mut self, order: SortOrder) -> Self {
601        self.order = Some(order);
602        self
603    }
604
605    /// Convert to query parameter pairs for the HTTP request.
606    pub fn to_query(&self) -> Vec<(String, String)> {
607        let mut q = Vec::new();
608        if let Some(ref after) = self.after {
609            q.push(("after".into(), after.clone()));
610        }
611        if let Some(ref before) = self.before {
612            q.push(("before".into(), before.clone()));
613        }
614        if let Some(limit) = self.limit {
615            q.push(("limit".into(), limit.to_string()));
616        }
617        if let Some(ref order) = self.order {
618            q.push((
619                "order".into(),
620                serde_json::to_value(order)
621                    .unwrap()
622                    .as_str()
623                    .unwrap()
624                    .to_string(),
625            ));
626        }
627        q
628    }
629}
630
631/// Parameters for listing runs with pagination.
632#[derive(Debug, Clone, Default)]
633pub struct RunListParams {
634    /// Cursor for pagination — fetch results after this run ID.
635    pub after: Option<String>,
636    /// Cursor for backward pagination — fetch results before this run ID.
637    pub before: Option<String>,
638    /// Maximum number of results per page (1–100).
639    pub limit: Option<i64>,
640    /// Sort order by `created_at`.
641    pub order: Option<SortOrder>,
642}
643
644impl RunListParams {
645    pub fn new() -> Self {
646        Self::default()
647    }
648
649    pub fn after(mut self, after: impl Into<String>) -> Self {
650        self.after = Some(after.into());
651        self
652    }
653
654    pub fn before(mut self, before: impl Into<String>) -> Self {
655        self.before = Some(before.into());
656        self
657    }
658
659    pub fn limit(mut self, limit: i64) -> Self {
660        self.limit = Some(limit);
661        self
662    }
663
664    pub fn order(mut self, order: SortOrder) -> Self {
665        self.order = Some(order);
666        self
667    }
668
669    /// Convert to query parameter pairs for the HTTP request.
670    pub fn to_query(&self) -> Vec<(String, String)> {
671        let mut q = Vec::new();
672        if let Some(ref after) = self.after {
673            q.push(("after".into(), after.clone()));
674        }
675        if let Some(ref before) = self.before {
676            q.push(("before".into(), before.clone()));
677        }
678        if let Some(limit) = self.limit {
679            q.push(("limit".into(), limit.to_string()));
680        }
681        if let Some(ref order) = self.order {
682            q.push((
683                "order".into(),
684                serde_json::to_value(order)
685                    .unwrap()
686                    .as_str()
687                    .unwrap()
688                    .to_string(),
689            ));
690        }
691        q
692    }
693}
694
695#[cfg(test)]
696mod tests {
697    use super::*;
698
699    #[test]
700    fn test_serialize_assistant_create() {
701        let req = AssistantCreateRequest::new("gpt-4o");
702        let json = serde_json::to_value(&req).unwrap();
703        assert_eq!(json["model"], "gpt-4o");
704    }
705
706    #[test]
707    fn test_serialize_assistant_with_tools() {
708        let mut req = AssistantCreateRequest::new("gpt-4o");
709        req.tools = Some(vec![
710            BetaTool::CodeInterpreter,
711            BetaTool::FileSearch { file_search: None },
712            BetaTool::Function {
713                function: BetaFunctionDef {
714                    name: "get_weather".into(),
715                    description: Some("Get weather".into()),
716                    parameters: Some(serde_json::json!({"type": "object"})),
717                },
718            },
719        ]);
720        let json = serde_json::to_value(&req).unwrap();
721        let tools = json["tools"].as_array().unwrap();
722        assert_eq!(tools.len(), 3);
723        assert_eq!(tools[0]["type"], "code_interpreter");
724        assert_eq!(tools[1]["type"], "file_search");
725        assert_eq!(tools[2]["type"], "function");
726        assert_eq!(tools[2]["function"]["name"], "get_weather");
727    }
728
729    #[test]
730    fn test_deserialize_assistant() {
731        let json = r#"{
732            "id": "asst_abc123",
733            "object": "assistant",
734            "created_at": 1699009709,
735            "model": "gpt-4o",
736            "tools": [{"type": "code_interpreter"}]
737        }"#;
738        let asst: Assistant = serde_json::from_str(json).unwrap();
739        assert_eq!(asst.id, "asst_abc123");
740        assert_eq!(asst.tools.len(), 1);
741        assert!(matches!(asst.tools[0], BetaTool::CodeInterpreter));
742    }
743
744    #[test]
745    fn test_deserialize_assistant_with_function_tool() {
746        let json = r#"{
747            "id": "asst_abc123",
748            "object": "assistant",
749            "created_at": 1699009709,
750            "model": "gpt-4o",
751            "tools": [{
752                "type": "function",
753                "function": {
754                    "name": "get_weather",
755                    "description": "Get current weather",
756                    "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
757                }
758            }]
759        }"#;
760        let asst: Assistant = serde_json::from_str(json).unwrap();
761        match &asst.tools[0] {
762            BetaTool::Function { function } => {
763                assert_eq!(function.name, "get_weather");
764            }
765            _ => panic!("expected function tool"),
766        }
767    }
768
769    #[test]
770    fn test_deserialize_thread() {
771        let json = r#"{
772            "id": "thread_abc123",
773            "object": "thread",
774            "created_at": 1699012949
775        }"#;
776        let thread: Thread = serde_json::from_str(json).unwrap();
777        assert_eq!(thread.id, "thread_abc123");
778    }
779
780    #[test]
781    fn test_deserialize_run() {
782        let json = r#"{
783            "id": "run_abc123",
784            "object": "thread.run",
785            "created_at": 1699012949,
786            "thread_id": "thread_abc123",
787            "assistant_id": "asst_abc123",
788            "status": "completed",
789            "tools": []
790        }"#;
791        let run: Run = serde_json::from_str(json).unwrap();
792        assert_eq!(run.status, RunStatus::Completed);
793    }
794
795    #[test]
796    fn test_deserialize_run_with_tools() {
797        let json = r#"{
798            "id": "run_abc123",
799            "object": "thread.run",
800            "created_at": 1699012949,
801            "thread_id": "thread_abc123",
802            "assistant_id": "asst_abc123",
803            "status": "completed",
804            "tools": [
805                {"type": "code_interpreter"},
806                {"type": "file_search", "file_search": {"max_num_results": 10}}
807            ]
808        }"#;
809        let run: Run = serde_json::from_str(json).unwrap();
810        assert_eq!(run.tools.len(), 2);
811        match &run.tools[1] {
812            BetaTool::FileSearch { file_search } => {
813                assert_eq!(file_search.as_ref().unwrap().max_num_results, Some(10));
814            }
815            _ => panic!("expected file_search tool"),
816        }
817    }
818
819    #[test]
820    fn test_deserialize_message_with_annotations() {
821        let json = r#"{
822            "id": "msg_abc123",
823            "object": "thread.message",
824            "created_at": 1699012949,
825            "thread_id": "thread_abc123",
826            "role": "assistant",
827            "content": [{
828                "type": "text",
829                "text": {
830                    "value": "See file [1].",
831                    "annotations": [{
832                        "type": "file_citation",
833                        "text": "[1]",
834                        "start_index": 9,
835                        "end_index": 12,
836                        "file_citation": {
837                            "file_id": "file-abc123",
838                            "quote": "relevant text"
839                        }
840                    }]
841                }
842            }]
843        }"#;
844        let msg: Message = serde_json::from_str(json).unwrap();
845        let text = msg.content[0].text.as_ref().unwrap();
846        assert_eq!(text.annotations.len(), 1);
847        assert_eq!(text.annotations[0].type_, "file_citation");
848        let citation = text.annotations[0].file_citation.as_ref().unwrap();
849        assert_eq!(citation.file_id, "file-abc123");
850    }
851
852    #[test]
853    fn test_deserialize_vector_store() {
854        let json = r#"{
855            "id": "vs_abc123",
856            "object": "vector_store",
857            "created_at": 1699012949,
858            "name": "My Store",
859            "status": "completed",
860            "file_counts": {
861                "in_progress": 0,
862                "completed": 5,
863                "failed": 0,
864                "cancelled": 0,
865                "total": 5
866            }
867        }"#;
868        let vs: VectorStore = serde_json::from_str(json).unwrap();
869        assert_eq!(vs.id, "vs_abc123");
870        assert_eq!(vs.file_counts.unwrap().completed, 5);
871    }
872}