1use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13use std::fmt;
14use std::str::FromStr;
15use uuid::Uuid;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(transparent)]
20pub struct FileId(pub Uuid);
21
22impl From<Uuid> for FileId {
23 fn from(uuid: Uuid) -> Self {
24 FileId(uuid)
25 }
26}
27
28impl std::ops::Deref for FileId {
29 type Target = Uuid;
30 fn deref(&self) -> &Self::Target {
31 &self.0
32 }
33}
34
35impl std::fmt::Display for FileId {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 write!(f, "{}", &self.0.to_string()[..8])
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
43#[serde(transparent)]
44pub struct BatchId(pub Uuid);
45
46impl From<Uuid> for BatchId {
47 fn from(uuid: Uuid) -> Self {
48 BatchId(uuid)
49 }
50}
51
52impl std::ops::Deref for BatchId {
53 type Target = Uuid;
54 fn deref(&self) -> &Self::Target {
55 &self.0
56 }
57}
58
59impl std::fmt::Display for BatchId {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(f, "{}", self.0)
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
67#[serde(transparent)]
68pub struct TemplateId(pub Uuid);
69
70impl From<Uuid> for TemplateId {
71 fn from(uuid: Uuid) -> Self {
72 TemplateId(uuid)
73 }
74}
75
76impl std::ops::Deref for TemplateId {
77 type Target = Uuid;
78 fn deref(&self) -> &Self::Target {
79 &self.0
80 }
81}
82
83impl std::fmt::Display for TemplateId {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 write!(f, "{}", &self.0.to_string()[..8])
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(rename_all = "snake_case")]
92pub enum Purpose {
93 Batch,
95 BatchOutput,
97 BatchError,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum OutputFileType {
104 Output,
106 Error,
108}
109
110impl fmt::Display for Purpose {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 Purpose::Batch => write!(f, "batch"),
114 Purpose::BatchOutput => write!(f, "batch_output"),
115 Purpose::BatchError => write!(f, "batch_error"),
116 }
117 }
118}
119
120impl FromStr for Purpose {
121 type Err = String;
122
123 fn from_str(s: &str) -> Result<Self, Self::Err> {
124 match s.to_lowercase().as_str() {
125 "batch" => Ok(Purpose::Batch),
126 "batch_output" => Ok(Purpose::BatchOutput),
127 "batch_error" => Ok(Purpose::BatchError),
128 _ => Err(format!("Invalid purpose: {}", s)),
129 }
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(rename_all = "lowercase")]
142pub enum FileStatus {
143 Processed,
145 Error,
147 Deleted,
149 Expired,
151}
152
153impl fmt::Display for FileStatus {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 match self {
156 FileStatus::Processed => write!(f, "processed"),
157 FileStatus::Error => write!(f, "error"),
158 FileStatus::Deleted => write!(f, "deleted"),
159 FileStatus::Expired => write!(f, "expired"),
160 }
161 }
162}
163
164impl FromStr for FileStatus {
165 type Err = String;
166
167 fn from_str(s: &str) -> Result<Self, Self::Err> {
168 match s.to_lowercase().as_str() {
169 "processed" => Ok(FileStatus::Processed),
170 "error" => Ok(FileStatus::Error),
171 "deleted" => Ok(FileStatus::Deleted),
172 "expired" => Ok(FileStatus::Expired),
173 _ => Err(format!("Invalid file status: {}", s)),
174 }
175 }
176}
177
178#[derive(Debug, Clone, Serialize)]
180pub struct File {
181 pub id: FileId,
182 pub name: String,
183 pub description: Option<String>,
184 pub size_bytes: i64,
185 pub status: FileStatus,
186 pub error_message: Option<String>,
187 pub purpose: Option<Purpose>,
188 pub expires_at: Option<DateTime<Utc>>,
189 pub deleted_at: Option<DateTime<Utc>>,
190 pub uploaded_by: Option<String>,
191 pub created_at: DateTime<Utc>,
192 pub updated_at: DateTime<Utc>,
193 pub size_finalized: bool,
194 pub api_key_id: Option<Uuid>,
196 pub source_connection_id: Option<Uuid>,
198 pub source_external_key: Option<String>,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
207pub struct RequestTemplate {
208 pub id: TemplateId,
209 pub file_id: FileId,
210 pub custom_id: Option<String>, pub endpoint: String,
212 pub method: String,
213 pub path: String,
214 pub body: String,
215 pub model: String,
216 pub api_key: String,
217 pub created_at: DateTime<Utc>,
218 pub updated_at: DateTime<Utc>,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
223pub struct RequestTemplateInput {
224 pub custom_id: Option<String>, pub endpoint: String,
226 pub method: String,
227 pub path: String,
228 pub body: String,
229 pub model: String,
230 pub api_key: String,
231}
232
233#[derive(Debug, Clone, Serialize, serde::Deserialize)]
235pub struct BatchOutputItem {
236 pub id: String,
238 pub custom_id: Option<String>,
240 pub response: BatchResponseDetails,
242 pub error: Option<serde_json::Value>,
244}
245
246#[derive(Debug, Clone, Serialize, serde::Deserialize)]
248pub struct BatchErrorItem {
249 pub id: String,
251 pub custom_id: Option<String>,
253 pub response: Option<serde_json::Value>,
255 pub error: BatchErrorDetails,
257}
258
259#[derive(Debug, Clone, Serialize, serde::Deserialize)]
261pub struct BatchResponseDetails {
262 pub status_code: i16,
264 pub request_id: Option<String>,
266 pub body: serde_json::Value,
268}
269
270#[derive(Debug, Clone, Serialize, serde::Deserialize)]
272pub struct BatchErrorDetails {
273 pub code: Option<String>,
275 pub message: String,
277}
278
279#[derive(Debug, Clone, Serialize)]
281#[serde(untagged)]
282pub enum FileContentItem {
283 Template(RequestTemplateInput),
285 Output(BatchOutputItem),
287 Error(BatchErrorItem),
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
293#[serde(rename_all = "snake_case")]
294pub enum BatchResultStatus {
295 Pending,
297 InProgress,
299 Completed,
301 Failed,
303}
304
305#[derive(Debug, Clone, Serialize)]
308pub struct BatchResultItem {
309 pub id: String,
311 pub custom_id: Option<String>,
313 pub model: String,
315 pub input_body: serde_json::Value,
317 pub response_body: Option<serde_json::Value>,
319 pub error: Option<String>,
321 pub status: BatchResultStatus,
323}
324
325#[derive(Debug, Clone, Default, Serialize)]
327pub struct FileMetadata {
328 pub filename: Option<String>,
329 pub description: Option<String>,
330 pub purpose: Option<String>,
331 pub expires_after_anchor: Option<String>,
332 pub expires_after_seconds: Option<i64>,
333 pub size_bytes: Option<i64>,
334 pub uploaded_by: Option<String>,
335 pub api_key_id: Option<Uuid>,
337 pub source_connection_id: Option<Uuid>,
339 pub source_external_key: Option<String>,
341}
342
343#[derive(Debug, Clone, Default)]
345pub struct FileFilter {
346 pub uploaded_by: Option<String>,
350 pub status: Option<String>,
352 pub purpose: Option<String>,
354 pub search: Option<String>,
356 pub after: Option<FileId>,
358 pub limit: Option<usize>,
360 pub api_key_ids: Option<Vec<Uuid>>,
364 pub ascending: bool,
366}
367
368#[derive(Debug, Clone, Default)]
370pub struct ListBatchesFilter {
371 pub created_by: Option<String>,
373 pub search: Option<String>,
375 pub after: Option<BatchId>,
377 pub limit: Option<i64>,
379 pub api_key_ids: Option<Vec<Uuid>>,
383 pub status: Option<String>,
385 pub created_after: Option<DateTime<Utc>>,
387 pub created_before: Option<DateTime<Utc>>,
389 pub active_first: bool,
395 pub completion_windows: Option<Vec<String>>,
400 pub service_tiers: Option<Vec<String>>,
404}
405
406#[derive(Debug, Clone, Serialize)]
408pub enum FileStreamItem {
409 Metadata(FileMetadata),
411 Template(RequestTemplateInput),
413 Abort,
415 #[deprecated(note = "Use FileStreamItem::Abort and retain the producer error locally instead")]
417 Error(String),
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
422pub enum FileStreamResult {
423 Success(FileId),
425 Aborted,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct BatchInput {
432 pub file_id: FileId,
434 pub endpoint: String,
436 pub completion_window: String,
438 pub metadata: Option<serde_json::Value>,
440 pub created_by: Option<String>,
442 pub api_key_id: Option<Uuid>,
444 pub api_key: Option<String>,
448 pub total_requests: Option<i64>,
452}
453
454#[derive(Debug, Clone, Serialize, Deserialize)]
459pub struct BackgroundBatchInput {
460 pub file_id: FileId,
462 pub endpoint: String,
464 pub metadata: Option<serde_json::Value>,
466 pub created_by: Option<String>,
468 pub api_key_id: Option<Uuid>,
470 pub api_key: Option<String>,
472 pub total_requests: Option<i64>,
474}
475
476#[derive(Debug, Clone, Serialize)]
478pub struct Batch {
479 pub id: BatchId,
480 pub file_id: Option<FileId>,
481 pub created_at: DateTime<Utc>,
482 pub metadata: Option<serde_json::Value>,
484 pub service_tier: Option<String>,
487 pub completion_window: Option<String>,
489 pub endpoint: String,
491 pub output_file_id: Option<FileId>,
493 pub error_file_id: Option<FileId>,
495 pub created_by: String,
497 pub expires_at: Option<DateTime<Utc>>,
500 pub cancelling_at: Option<DateTime<Utc>>,
502 pub errors: Option<serde_json::Value>,
504
505 pub total_requests: i64,
507 pub pending_requests: i64,
508 pub in_progress_requests: i64,
509 pub completed_requests: i64,
510 pub failed_requests: i64,
511 pub canceled_requests: i64,
512 pub requests_started_at: Option<DateTime<Utc>>,
513
514 pub finalizing_at: Option<DateTime<Utc>>,
516 pub completed_at: Option<DateTime<Utc>>,
517 pub failed_at: Option<DateTime<Utc>>,
518 pub cancelled_at: Option<DateTime<Utc>>,
519
520 pub deleted_at: Option<DateTime<Utc>>,
522
523 pub notification_sent_at: Option<DateTime<Utc>>,
525
526 pub api_key_id: Option<Uuid>,
528}
529
530#[derive(Debug, Clone)]
533pub struct BatchNotification {
534 pub batch: Batch,
535 pub model: String,
536 pub input_file_name: Option<String>,
537 pub input_file_description: Option<String>,
538}
539
540#[derive(Debug, Clone, Serialize)]
542pub struct BatchStatus {
543 pub batch_id: BatchId,
544 pub file_id: Option<FileId>,
545 pub file_name: Option<String>,
546 pub total_requests: i64,
547 pub pending_requests: i64,
548 pub in_progress_requests: i64,
549 pub completed_requests: i64,
550 pub failed_requests: i64,
551 pub canceled_requests: i64,
552 pub started_at: Option<DateTime<Utc>>,
553 pub failed_at: Option<DateTime<Utc>>,
554 pub created_at: DateTime<Utc>,
555}
556
557impl BatchStatus {
558 pub fn is_finished(&self) -> bool {
560 self.completed_requests + self.failed_requests + self.canceled_requests
561 == self.total_requests
562 }
563
564 pub fn is_running(&self) -> bool {
566 !self.is_finished()
567 }
568
569 pub fn openai_status(&self) -> &'static str {
578 if self.failed_at.is_some() {
579 return "failed";
580 }
581
582 if self.started_at.is_none() {
583 return "validating";
586 }
587
588 let terminal_count =
589 self.completed_requests + self.failed_requests + self.canceled_requests;
590
591 if terminal_count == 0 {
592 "in_progress"
594 } else if terminal_count == self.total_requests {
595 if self.canceled_requests == self.total_requests {
597 "cancelled"
598 } else if self.completed_requests == 0 {
599 "failed"
600 } else {
601 "completed"
602 }
603 } else if terminal_count as f64 / self.total_requests as f64 >= 0.95 {
604 "finalizing"
606 } else {
607 "in_progress"
609 }
610 }
611}
612
613#[derive(Debug, Clone, Serialize, Deserialize)]
616pub struct ModelTemplateStats {
617 pub model: String,
619 pub request_count: i64,
621 pub total_body_bytes: i64,
623}
624
625#[cfg(test)]
626mod background_tests {
627 use super::*;
628
629 #[test]
630 fn background_batch_input_does_not_require_a_completion_window() {
631 let file_id = FileId(Uuid::new_v4());
632 let input = BackgroundBatchInput {
633 file_id,
634 endpoint: "/v1/responses".to_string(),
635 metadata: None,
636 created_by: Some("user-a".to_string()),
637 api_key_id: None,
638 api_key: Some("test-key".to_string()),
639 total_requests: Some(2),
640 };
641
642 assert_eq!(input.file_id, file_id);
643 assert_eq!(input.total_requests, Some(2));
644 }
645
646 #[test]
647 fn background_batch_serializes_without_a_deadline() {
648 let batch = Batch {
649 id: BatchId(Uuid::new_v4()),
650 file_id: Some(FileId(Uuid::new_v4())),
651 created_at: Utc::now(),
652 metadata: None,
653 service_tier: Some("background".to_string()),
654 completion_window: None,
655 endpoint: "/v1/responses".to_string(),
656 output_file_id: None,
657 error_file_id: None,
658 created_by: "user-a".to_string(),
659 expires_at: None,
660 cancelling_at: None,
661 errors: None,
662 total_requests: 0,
663 pending_requests: 0,
664 in_progress_requests: 0,
665 completed_requests: 0,
666 failed_requests: 0,
667 canceled_requests: 0,
668 requests_started_at: None,
669 finalizing_at: None,
670 completed_at: None,
671 failed_at: None,
672 cancelled_at: None,
673 deleted_at: None,
674 notification_sent_at: None,
675 api_key_id: None,
676 };
677
678 let json = serde_json::to_value(batch).expect("batch should serialize");
679 assert_eq!(json["service_tier"], "background");
680 assert!(json["completion_window"].is_null());
681 assert!(json["expires_at"].is_null());
682 }
683}