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;
6
7// ── Tool types ──
8
9/// A tool available to an assistant or run.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(tag = "type")]
12#[non_exhaustive]
13pub enum BetaTool {
14    /// Code interpreter tool.
15    #[serde(rename = "code_interpreter")]
16    CodeInterpreter,
17    /// File search tool with optional configuration.
18    #[serde(rename = "file_search")]
19    FileSearch {
20        #[serde(skip_serializing_if = "Option::is_none")]
21        file_search: Option<FileSearchConfig>,
22    },
23    /// Function tool.
24    #[serde(rename = "function")]
25    Function { function: BetaFunctionDef },
26}
27
28/// Configuration for the file search tool.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct FileSearchConfig {
31    /// Maximum number of results (1–50).
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub max_num_results: Option<i64>,
34    /// Ranking options.
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub ranking_options: Option<FileSearchRankingOptions>,
37}
38
39/// Ranking options for file search.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct FileSearchRankingOptions {
42    /// Score threshold (0.0–1.0).
43    pub score_threshold: f64,
44    /// Ranker to use: "auto" or "default_2024_08_21".
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub ranker: Option<String>,
47}
48
49/// Function definition within a beta tool.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct BetaFunctionDef {
52    pub name: String,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub description: Option<String>,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub parameters: Option<serde_json::Value>,
57}
58
59/// An annotation on message text (file citation or file path).
60#[derive(Debug, Clone, Deserialize)]
61pub struct MessageAnnotation {
62    /// Annotation type: "file_citation" or "file_path".
63    #[serde(rename = "type")]
64    pub type_: String,
65    /// The text in the message content being annotated.
66    #[serde(default)]
67    pub text: Option<String>,
68    /// Start index of the annotation in the text.
69    #[serde(default)]
70    pub start_index: Option<i64>,
71    /// End index of the annotation in the text.
72    #[serde(default)]
73    pub end_index: Option<i64>,
74    /// File citation details (for file_citation type).
75    #[serde(default)]
76    pub file_citation: Option<FileCitation>,
77    /// File path details (for file_path type).
78    #[serde(default)]
79    pub file_path: Option<FilePath>,
80}
81
82/// File citation in an annotation.
83#[derive(Debug, Clone, Deserialize)]
84pub struct FileCitation {
85    pub file_id: String,
86    #[serde(default)]
87    pub quote: Option<String>,
88}
89
90/// File path in an annotation.
91#[derive(Debug, Clone, Deserialize)]
92pub struct FilePath {
93    pub file_id: String,
94}
95
96// ── Assistants ──
97
98/// Request body for creating an assistant.
99#[derive(Debug, Clone, Serialize)]
100pub struct AssistantCreateRequest {
101    pub model: String,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub name: Option<String>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub description: Option<String>,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub instructions: Option<String>,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub tools: Option<Vec<BetaTool>>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub metadata: Option<std::collections::HashMap<String, String>>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub temperature: Option<f64>,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub top_p: Option<f64>,
116}
117
118impl AssistantCreateRequest {
119    pub fn new(model: impl Into<String>) -> Self {
120        Self {
121            model: model.into(),
122            name: None,
123            description: None,
124            instructions: None,
125            tools: None,
126            metadata: None,
127            temperature: None,
128            top_p: None,
129        }
130    }
131}
132
133/// An assistant object.
134#[derive(Debug, Clone, Deserialize)]
135pub struct Assistant {
136    pub id: String,
137    pub object: String,
138    pub created_at: i64,
139    pub model: String,
140    #[serde(default)]
141    pub name: Option<String>,
142    #[serde(default)]
143    pub description: Option<String>,
144    #[serde(default)]
145    pub instructions: Option<String>,
146    #[serde(default)]
147    pub tools: Vec<BetaTool>,
148    #[serde(default)]
149    pub metadata: Option<std::collections::HashMap<String, String>>,
150    #[serde(default)]
151    pub temperature: Option<f64>,
152    #[serde(default)]
153    pub top_p: Option<f64>,
154}
155
156/// List of assistants.
157#[derive(Debug, Clone, Deserialize)]
158pub struct AssistantList {
159    pub object: String,
160    pub data: Vec<Assistant>,
161}
162
163/// Deleted assistant response.
164#[derive(Debug, Clone, Deserialize)]
165pub struct AssistantDeleted {
166    pub id: String,
167    pub deleted: bool,
168    pub object: String,
169}
170
171// ── Threads ──
172
173/// Request body for creating a thread.
174#[derive(Debug, Clone, Default, Serialize)]
175pub struct ThreadCreateRequest {
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub messages: Option<Vec<ThreadMessage>>,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub metadata: Option<std::collections::HashMap<String, String>>,
180}
181
182/// A message in a thread creation request.
183#[derive(Debug, Clone, Serialize)]
184pub struct ThreadMessage {
185    pub role: Role,
186    pub content: String,
187}
188
189/// A thread object.
190#[derive(Debug, Clone, Deserialize)]
191pub struct Thread {
192    pub id: String,
193    pub object: String,
194    pub created_at: i64,
195    #[serde(default)]
196    pub metadata: Option<std::collections::HashMap<String, String>>,
197}
198
199/// Deleted thread response.
200#[derive(Debug, Clone, Deserialize)]
201pub struct ThreadDeleted {
202    pub id: String,
203    pub deleted: bool,
204    pub object: String,
205}
206
207/// A message within a thread.
208#[derive(Debug, Clone, Deserialize)]
209pub struct Message {
210    pub id: String,
211    pub object: String,
212    pub created_at: i64,
213    pub thread_id: String,
214    pub role: Role,
215    pub content: Vec<MessageContent>,
216    #[serde(default)]
217    pub assistant_id: Option<String>,
218    #[serde(default)]
219    pub run_id: Option<String>,
220    #[serde(default)]
221    pub metadata: Option<std::collections::HashMap<String, String>>,
222}
223
224/// Content block in a message.
225#[derive(Debug, Clone, Deserialize)]
226pub struct MessageContent {
227    #[serde(rename = "type")]
228    pub type_: String,
229    #[serde(default)]
230    pub text: Option<MessageText>,
231}
232
233/// Text content in a message.
234#[derive(Debug, Clone, Deserialize)]
235pub struct MessageText {
236    pub value: String,
237    #[serde(default)]
238    pub annotations: Vec<MessageAnnotation>,
239}
240
241/// Request body for creating a message.
242#[derive(Debug, Clone, Serialize)]
243pub struct MessageCreateRequest {
244    pub role: Role,
245    pub content: String,
246}
247
248/// List of messages.
249#[derive(Debug, Clone, Deserialize)]
250pub struct MessageList {
251    pub object: String,
252    pub data: Vec<Message>,
253}
254
255// ── Runs ──
256
257/// Request body for creating a run.
258#[derive(Debug, Clone, Serialize)]
259pub struct RunCreateRequest {
260    pub assistant_id: String,
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub instructions: Option<String>,
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub tools: Option<Vec<BetaTool>>,
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub metadata: Option<std::collections::HashMap<String, String>>,
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub model: Option<String>,
269}
270
271impl RunCreateRequest {
272    pub fn new(assistant_id: impl Into<String>) -> Self {
273        Self {
274            assistant_id: assistant_id.into(),
275            instructions: None,
276            tools: None,
277            metadata: None,
278            model: None,
279        }
280    }
281}
282
283/// A run object.
284#[derive(Debug, Clone, Deserialize)]
285pub struct Run {
286    pub id: String,
287    pub object: String,
288    pub created_at: i64,
289    pub thread_id: String,
290    pub assistant_id: String,
291    pub status: String,
292    #[serde(default)]
293    pub model: Option<String>,
294    #[serde(default)]
295    pub instructions: Option<String>,
296    #[serde(default)]
297    pub tools: Vec<BetaTool>,
298    #[serde(default)]
299    pub started_at: Option<i64>,
300    #[serde(default)]
301    pub completed_at: Option<i64>,
302    #[serde(default)]
303    pub cancelled_at: Option<i64>,
304    #[serde(default)]
305    pub failed_at: Option<i64>,
306    #[serde(default)]
307    pub metadata: Option<std::collections::HashMap<String, String>>,
308}
309
310/// Submit tool outputs request.
311#[derive(Debug, Clone, Serialize)]
312pub struct SubmitToolOutputsRequest {
313    pub tool_outputs: Vec<ToolOutput>,
314}
315
316/// A single tool output.
317#[derive(Debug, Clone, Serialize)]
318pub struct ToolOutput {
319    pub tool_call_id: String,
320    pub output: String,
321}
322
323// ── Vector Stores ──
324
325/// Request body for creating a vector store.
326#[derive(Debug, Clone, Default, Serialize)]
327pub struct VectorStoreCreateRequest {
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub name: Option<String>,
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub file_ids: Option<Vec<String>>,
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub metadata: Option<std::collections::HashMap<String, String>>,
334}
335
336/// A vector store object.
337#[derive(Debug, Clone, Deserialize)]
338pub struct VectorStore {
339    pub id: String,
340    pub object: String,
341    pub created_at: i64,
342    #[serde(default)]
343    pub name: Option<String>,
344    pub status: String,
345    #[serde(default)]
346    pub file_counts: Option<VectorStoreFileCounts>,
347    #[serde(default)]
348    pub metadata: Option<std::collections::HashMap<String, String>>,
349}
350
351/// File counts for a vector store.
352#[derive(Debug, Clone, Deserialize)]
353pub struct VectorStoreFileCounts {
354    pub in_progress: i64,
355    pub completed: i64,
356    pub failed: i64,
357    pub cancelled: i64,
358    pub total: i64,
359}
360
361/// List of vector stores.
362#[derive(Debug, Clone, Deserialize)]
363pub struct VectorStoreList {
364    pub object: String,
365    pub data: Vec<VectorStore>,
366}
367
368/// Deleted vector store response.
369#[derive(Debug, Clone, Deserialize)]
370pub struct VectorStoreDeleted {
371    pub id: String,
372    pub deleted: bool,
373    pub object: String,
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379
380    #[test]
381    fn test_serialize_assistant_create() {
382        let req = AssistantCreateRequest::new("gpt-4o");
383        let json = serde_json::to_value(&req).unwrap();
384        assert_eq!(json["model"], "gpt-4o");
385    }
386
387    #[test]
388    fn test_serialize_assistant_with_tools() {
389        let mut req = AssistantCreateRequest::new("gpt-4o");
390        req.tools = Some(vec![
391            BetaTool::CodeInterpreter,
392            BetaTool::FileSearch { file_search: None },
393            BetaTool::Function {
394                function: BetaFunctionDef {
395                    name: "get_weather".into(),
396                    description: Some("Get weather".into()),
397                    parameters: Some(serde_json::json!({"type": "object"})),
398                },
399            },
400        ]);
401        let json = serde_json::to_value(&req).unwrap();
402        let tools = json["tools"].as_array().unwrap();
403        assert_eq!(tools.len(), 3);
404        assert_eq!(tools[0]["type"], "code_interpreter");
405        assert_eq!(tools[1]["type"], "file_search");
406        assert_eq!(tools[2]["type"], "function");
407        assert_eq!(tools[2]["function"]["name"], "get_weather");
408    }
409
410    #[test]
411    fn test_deserialize_assistant() {
412        let json = r#"{
413            "id": "asst_abc123",
414            "object": "assistant",
415            "created_at": 1699009709,
416            "model": "gpt-4o",
417            "tools": [{"type": "code_interpreter"}]
418        }"#;
419        let asst: Assistant = serde_json::from_str(json).unwrap();
420        assert_eq!(asst.id, "asst_abc123");
421        assert_eq!(asst.tools.len(), 1);
422        assert!(matches!(asst.tools[0], BetaTool::CodeInterpreter));
423    }
424
425    #[test]
426    fn test_deserialize_assistant_with_function_tool() {
427        let json = r#"{
428            "id": "asst_abc123",
429            "object": "assistant",
430            "created_at": 1699009709,
431            "model": "gpt-4o",
432            "tools": [{
433                "type": "function",
434                "function": {
435                    "name": "get_weather",
436                    "description": "Get current weather",
437                    "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
438                }
439            }]
440        }"#;
441        let asst: Assistant = serde_json::from_str(json).unwrap();
442        match &asst.tools[0] {
443            BetaTool::Function { function } => {
444                assert_eq!(function.name, "get_weather");
445            }
446            _ => panic!("expected function tool"),
447        }
448    }
449
450    #[test]
451    fn test_deserialize_thread() {
452        let json = r#"{
453            "id": "thread_abc123",
454            "object": "thread",
455            "created_at": 1699012949
456        }"#;
457        let thread: Thread = serde_json::from_str(json).unwrap();
458        assert_eq!(thread.id, "thread_abc123");
459    }
460
461    #[test]
462    fn test_deserialize_run() {
463        let json = r#"{
464            "id": "run_abc123",
465            "object": "thread.run",
466            "created_at": 1699012949,
467            "thread_id": "thread_abc123",
468            "assistant_id": "asst_abc123",
469            "status": "completed",
470            "tools": []
471        }"#;
472        let run: Run = serde_json::from_str(json).unwrap();
473        assert_eq!(run.status, "completed");
474    }
475
476    #[test]
477    fn test_deserialize_run_with_tools() {
478        let json = r#"{
479            "id": "run_abc123",
480            "object": "thread.run",
481            "created_at": 1699012949,
482            "thread_id": "thread_abc123",
483            "assistant_id": "asst_abc123",
484            "status": "completed",
485            "tools": [
486                {"type": "code_interpreter"},
487                {"type": "file_search", "file_search": {"max_num_results": 10}}
488            ]
489        }"#;
490        let run: Run = serde_json::from_str(json).unwrap();
491        assert_eq!(run.tools.len(), 2);
492        match &run.tools[1] {
493            BetaTool::FileSearch { file_search } => {
494                assert_eq!(file_search.as_ref().unwrap().max_num_results, Some(10));
495            }
496            _ => panic!("expected file_search tool"),
497        }
498    }
499
500    #[test]
501    fn test_deserialize_message_with_annotations() {
502        let json = r#"{
503            "id": "msg_abc123",
504            "object": "thread.message",
505            "created_at": 1699012949,
506            "thread_id": "thread_abc123",
507            "role": "assistant",
508            "content": [{
509                "type": "text",
510                "text": {
511                    "value": "See file [1].",
512                    "annotations": [{
513                        "type": "file_citation",
514                        "text": "[1]",
515                        "start_index": 9,
516                        "end_index": 12,
517                        "file_citation": {
518                            "file_id": "file-abc123",
519                            "quote": "relevant text"
520                        }
521                    }]
522                }
523            }]
524        }"#;
525        let msg: Message = serde_json::from_str(json).unwrap();
526        let text = msg.content[0].text.as_ref().unwrap();
527        assert_eq!(text.annotations.len(), 1);
528        assert_eq!(text.annotations[0].type_, "file_citation");
529        let citation = text.annotations[0].file_citation.as_ref().unwrap();
530        assert_eq!(citation.file_id, "file-abc123");
531    }
532
533    #[test]
534    fn test_deserialize_vector_store() {
535        let json = r#"{
536            "id": "vs_abc123",
537            "object": "vector_store",
538            "created_at": 1699012949,
539            "name": "My Store",
540            "status": "completed",
541            "file_counts": {
542                "in_progress": 0,
543                "completed": 5,
544                "failed": 0,
545                "cancelled": 0,
546                "total": 5
547            }
548        }"#;
549        let vs: VectorStore = serde_json::from_str(json).unwrap();
550        assert_eq!(vs.id, "vs_abc123");
551        assert_eq!(vs.file_counts.unwrap().completed, 5);
552    }
553}