openai_api_rs/v1/
api.rs

1use crate::v1::assistant::{
2    AssistantFileObject, AssistantFileRequest, AssistantObject, AssistantRequest, ListAssistant,
3    ListAssistantFile,
4};
5use crate::v1::audio::{
6    AudioSpeechRequest, AudioSpeechResponse, AudioTranscriptionRequest, AudioTranscriptionResponse,
7    AudioTranslationRequest, AudioTranslationResponse,
8};
9use crate::v1::batch::{BatchResponse, CreateBatchRequest, ListBatchResponse};
10use crate::v1::chat_completion::{ChatCompletionRequest, ChatCompletionResponse};
11use crate::v1::common;
12use crate::v1::completion::{CompletionRequest, CompletionResponse};
13use crate::v1::edit::{EditRequest, EditResponse};
14use crate::v1::embedding::{EmbeddingRequest, EmbeddingResponse};
15use crate::v1::error::APIError;
16use crate::v1::file::{
17    FileDeleteRequest, FileDeleteResponse, FileListResponse, FileRetrieveResponse,
18    FileUploadRequest, FileUploadResponse,
19};
20use crate::v1::fine_tuning::{
21    CancelFineTuningJobRequest, CreateFineTuningJobRequest, FineTuningJobEvent,
22    FineTuningJobObject, FineTuningPagination, ListFineTuningJobEventsRequest,
23    RetrieveFineTuningJobRequest,
24};
25use crate::v1::image::{
26    ImageEditRequest, ImageEditResponse, ImageGenerationRequest, ImageGenerationResponse,
27    ImageVariationRequest, ImageVariationResponse,
28};
29use crate::v1::message::{
30    CreateMessageRequest, ListMessage, ListMessageFile, MessageFileObject, MessageObject,
31    ModifyMessageRequest,
32};
33use crate::v1::model::{ModelResponse, ModelsResponse};
34use crate::v1::moderation::{CreateModerationRequest, CreateModerationResponse};
35use crate::v1::run::{
36    CreateRunRequest, CreateThreadAndRunRequest, ListRun, ListRunStep, ModifyRunRequest, RunObject,
37    RunStepObject,
38};
39use crate::v1::thread::{CreateThreadRequest, ModifyThreadRequest, ThreadObject};
40
41use bytes::Bytes;
42use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
43use reqwest::multipart::{Form, Part};
44use reqwest::{Client, Method, Response};
45use serde::Serialize;
46use serde_json::Value;
47use url::Url;
48
49use std::error::Error;
50use std::fs::{create_dir_all, File};
51use std::io::Read;
52use std::io::Write;
53use std::path::Path;
54
55const API_URL_V1: &str = "https://api.openai.com/v1";
56
57#[derive(Default)]
58pub struct OpenAIClientBuilder {
59    api_endpoint: Option<String>,
60    api_key: Option<String>,
61    organization: Option<String>,
62    proxy: Option<String>,
63    timeout: Option<u64>,
64    headers: Option<HeaderMap>,
65}
66
67#[derive(Debug)]
68pub struct OpenAIClient {
69    api_endpoint: String,
70    api_key: Option<String>,
71    organization: Option<String>,
72    proxy: Option<String>,
73    timeout: Option<u64>,
74    headers: Option<HeaderMap>,
75    pub response_headers: Option<HeaderMap>,
76}
77
78impl OpenAIClientBuilder {
79    pub fn new() -> Self {
80        Self::default()
81    }
82
83    pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
84        self.api_key = Some(api_key.into());
85        self
86    }
87
88    pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
89        self.api_endpoint = Some(endpoint.into());
90        self
91    }
92
93    pub fn with_organization(mut self, organization: impl Into<String>) -> Self {
94        self.organization = Some(organization.into());
95        self
96    }
97
98    pub fn with_proxy(mut self, proxy: impl Into<String>) -> Self {
99        self.proxy = Some(proxy.into());
100        self
101    }
102
103    pub fn with_timeout(mut self, timeout: u64) -> Self {
104        self.timeout = Some(timeout);
105        self
106    }
107
108    pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
109        let headers = self.headers.get_or_insert_with(HeaderMap::new);
110        headers.insert(
111            HeaderName::from_bytes(key.into().as_bytes()).expect("Invalid header name"),
112            HeaderValue::from_str(&value.into()).expect("Invalid header value"),
113        );
114        self
115    }
116
117    pub fn build(self) -> Result<OpenAIClient, Box<dyn Error>> {
118        let api_endpoint = self.api_endpoint.unwrap_or_else(|| {
119            std::env::var("OPENAI_API_BASE").unwrap_or_else(|_| API_URL_V1.to_owned())
120        });
121
122        Ok(OpenAIClient {
123            api_endpoint,
124            api_key: self.api_key,
125            organization: self.organization,
126            proxy: self.proxy,
127            timeout: self.timeout,
128            headers: self.headers,
129            response_headers: None,
130        })
131    }
132}
133
134impl OpenAIClient {
135    pub fn builder() -> OpenAIClientBuilder {
136        OpenAIClientBuilder::new()
137    }
138
139    async fn build_request(&self, method: Method, path: &str) -> reqwest::RequestBuilder {
140        let url = self
141            .build_url_with_preserved_query(path)
142            .unwrap_or_else(|_| format!("{}/{}", self.api_endpoint, path));
143
144        let client = Client::builder();
145
146        #[cfg(feature = "rustls")]
147        let client = client.use_rustls_tls();
148
149        let client = if let Some(timeout) = self.timeout {
150            client.timeout(std::time::Duration::from_secs(timeout))
151        } else {
152            client
153        };
154
155        let client = if let Some(proxy) = &self.proxy {
156            client.proxy(reqwest::Proxy::all(proxy).unwrap())
157        } else {
158            client
159        };
160
161        let client = client.build().unwrap();
162
163        let mut request = client.request(method, url);
164
165        if let Some(api_key) = &self.api_key {
166            request = request.header("Authorization", format!("Bearer {api_key}"));
167        }
168
169        if let Some(organization) = &self.organization {
170            request = request.header("openai-organization", organization);
171        }
172
173        if let Some(headers) = &self.headers {
174            for (key, value) in headers {
175                request = request.header(key, value);
176            }
177        }
178
179        if Self::is_beta(path) {
180            request = request.header("OpenAI-Beta", "assistants=v2");
181        }
182
183        request
184    }
185
186    async fn post<T: serde::de::DeserializeOwned>(
187        &mut self,
188        path: &str,
189        body: &impl serde::ser::Serialize,
190    ) -> Result<T, APIError> {
191        let request = self.build_request(Method::POST, path).await;
192        let request = request.json(body);
193        let response = request.send().await?;
194        self.handle_response(response).await
195    }
196
197    async fn get<T: serde::de::DeserializeOwned>(&mut self, path: &str) -> Result<T, APIError> {
198        let request = self.build_request(Method::GET, path).await;
199        let response = request.send().await?;
200        self.handle_response(response).await
201    }
202
203    async fn get_raw(&self, path: &str) -> Result<Bytes, APIError> {
204        let request = self.build_request(Method::GET, path).await;
205        let response = request.send().await?;
206        Ok(response.bytes().await?)
207    }
208
209    async fn delete<T: serde::de::DeserializeOwned>(&mut self, path: &str) -> Result<T, APIError> {
210        let request = self.build_request(Method::DELETE, path).await;
211        let response = request.send().await?;
212        self.handle_response(response).await
213    }
214
215    async fn post_form<T: serde::de::DeserializeOwned>(
216        &mut self,
217        path: &str,
218        form: Form,
219    ) -> Result<T, APIError> {
220        let request = self.build_request(Method::POST, path).await;
221        let request = request.multipart(form);
222        let response = request.send().await?;
223        self.handle_response(response).await
224    }
225
226    async fn post_form_raw(&self, path: &str, form: Form) -> Result<Bytes, APIError> {
227        let request = self.build_request(Method::POST, path).await;
228        let request = request.multipart(form);
229        let response = request.send().await?;
230        Ok(response.bytes().await?)
231    }
232
233    async fn handle_response<T: serde::de::DeserializeOwned>(
234        &mut self,
235        response: Response,
236    ) -> Result<T, APIError> {
237        let status = response.status();
238        let headers = response.headers().clone();
239        if status.is_success() {
240            let text = response.text().await.unwrap_or_else(|_| "".to_string());
241            match serde_json::from_str::<T>(&text) {
242                Ok(parsed) => {
243                    self.response_headers = Some(headers);
244                    Ok(parsed)
245                }
246                Err(e) => Err(APIError::CustomError {
247                    message: format!("Failed to parse JSON: {e} / response {text}"),
248                }),
249            }
250        } else {
251            let error_message = response
252                .text()
253                .await
254                .unwrap_or_else(|_| "Unknown error".to_string());
255            Err(APIError::CustomError {
256                message: format!("{status}: {error_message}"),
257            })
258        }
259    }
260
261    pub async fn completion(
262        &mut self,
263        req: CompletionRequest,
264    ) -> Result<CompletionResponse, APIError> {
265        self.post("completions", &req).await
266    }
267
268    pub async fn edit(&mut self, req: EditRequest) -> Result<EditResponse, APIError> {
269        self.post("edits", &req).await
270    }
271
272    pub async fn image_generation(
273        &mut self,
274        req: ImageGenerationRequest,
275    ) -> Result<ImageGenerationResponse, APIError> {
276        self.post("images/generations", &req).await
277    }
278
279    pub async fn image_edit(
280        &mut self,
281        req: ImageEditRequest,
282    ) -> Result<ImageEditResponse, APIError> {
283        self.post("images/edits", &req).await
284    }
285
286    pub async fn image_variation(
287        &mut self,
288        req: ImageVariationRequest,
289    ) -> Result<ImageVariationResponse, APIError> {
290        self.post("images/variations", &req).await
291    }
292
293    pub async fn embedding(
294        &mut self,
295        req: EmbeddingRequest,
296    ) -> Result<EmbeddingResponse, APIError> {
297        self.post("embeddings", &req).await
298    }
299
300    pub async fn file_list(&mut self) -> Result<FileListResponse, APIError> {
301        self.get("files").await
302    }
303
304    pub async fn upload_file(
305        &mut self,
306        req: FileUploadRequest,
307    ) -> Result<FileUploadResponse, APIError> {
308        let form = Self::create_form(&req, "file")?;
309        self.post_form("files", form).await
310    }
311
312    pub async fn delete_file(
313        &mut self,
314        req: FileDeleteRequest,
315    ) -> Result<FileDeleteResponse, APIError> {
316        self.delete(&format!("files/{}", req.file_id)).await
317    }
318
319    pub async fn retrieve_file(
320        &mut self,
321        file_id: String,
322    ) -> Result<FileRetrieveResponse, APIError> {
323        self.get(&format!("files/{file_id}")).await
324    }
325
326    pub async fn retrieve_file_content(&self, file_id: String) -> Result<Bytes, APIError> {
327        self.get_raw(&format!("files/{file_id}/content")).await
328    }
329
330    pub async fn chat_completion(
331        &mut self,
332        req: ChatCompletionRequest,
333    ) -> Result<ChatCompletionResponse, APIError> {
334        self.post("chat/completions", &req).await
335    }
336
337    pub async fn audio_transcription(
338        &mut self,
339        req: AudioTranscriptionRequest,
340    ) -> Result<AudioTranscriptionResponse, APIError> {
341        // https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-response_format
342        if let Some(response_format) = &req.response_format {
343            if response_format != "json" && response_format != "verbose_json" {
344                return Err(APIError::CustomError {
345                    message: "response_format must be either 'json' or 'verbose_json' please use audio_transcription_raw".to_string(),
346                });
347            }
348        }
349        let form: Form;
350        if req.clone().file.is_some() {
351            form = Self::create_form(&req, "file")?;
352        } else if let Some(bytes) = req.clone().bytes {
353            form = Self::create_form_from_bytes(&req, bytes)?;
354        } else {
355            return Err(APIError::CustomError {
356                message: "Either file or bytes must be provided".to_string(),
357            });
358        }
359        self.post_form("audio/transcriptions", form).await
360    }
361
362    pub async fn audio_transcription_raw(
363        &mut self,
364        req: AudioTranscriptionRequest,
365    ) -> Result<Bytes, APIError> {
366        // https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-response_format
367        if let Some(response_format) = &req.response_format {
368            if response_format != "text" && response_format != "srt" && response_format != "vtt" {
369                return Err(APIError::CustomError {
370                    message: "response_format must be either 'text', 'srt' or 'vtt', please use audio_transcription".to_string(),
371                });
372            }
373        }
374        let form: Form;
375        if req.clone().file.is_some() {
376            form = Self::create_form(&req, "file")?;
377        } else if let Some(bytes) = req.clone().bytes {
378            form = Self::create_form_from_bytes(&req, bytes)?;
379        } else {
380            return Err(APIError::CustomError {
381                message: "Either file or bytes must be provided".to_string(),
382            });
383        }
384        self.post_form_raw("audio/transcriptions", form).await
385    }
386
387    pub async fn audio_translation(
388        &mut self,
389        req: AudioTranslationRequest,
390    ) -> Result<AudioTranslationResponse, APIError> {
391        let form = Self::create_form(&req, "file")?;
392        self.post_form("audio/translations", form).await
393    }
394
395    pub async fn audio_speech(
396        &mut self,
397        req: AudioSpeechRequest,
398    ) -> Result<AudioSpeechResponse, APIError> {
399        let request = self.build_request(Method::POST, "audio/speech").await;
400        let request = request.json(&req);
401        let response = request.send().await?;
402        let headers = response.headers().clone();
403        let bytes = response.bytes().await?;
404        let path = Path::new(req.output.as_str());
405        if let Some(parent) = path.parent() {
406            match create_dir_all(parent) {
407                Ok(_) => {}
408                Err(e) => {
409                    return Err(APIError::CustomError {
410                        message: e.to_string(),
411                    })
412                }
413            }
414        }
415        match File::create(path) {
416            Ok(mut file) => match file.write_all(&bytes) {
417                Ok(_) => {}
418                Err(e) => {
419                    return Err(APIError::CustomError {
420                        message: e.to_string(),
421                    })
422                }
423            },
424            Err(e) => {
425                return Err(APIError::CustomError {
426                    message: e.to_string(),
427                })
428            }
429        }
430
431        Ok(AudioSpeechResponse {
432            result: true,
433            headers: Some(headers),
434        })
435    }
436
437    pub async fn create_fine_tuning_job(
438        &mut self,
439        req: CreateFineTuningJobRequest,
440    ) -> Result<FineTuningJobObject, APIError> {
441        self.post("fine_tuning/jobs", &req).await
442    }
443
444    pub async fn list_fine_tuning_jobs(
445        &mut self,
446    ) -> Result<FineTuningPagination<FineTuningJobObject>, APIError> {
447        self.get("fine_tuning/jobs").await
448    }
449
450    pub async fn list_fine_tuning_job_events(
451        &mut self,
452        req: ListFineTuningJobEventsRequest,
453    ) -> Result<FineTuningPagination<FineTuningJobEvent>, APIError> {
454        self.get(&format!(
455            "fine_tuning/jobs/{}/events",
456            req.fine_tuning_job_id
457        ))
458        .await
459    }
460
461    pub async fn retrieve_fine_tuning_job(
462        &mut self,
463        req: RetrieveFineTuningJobRequest,
464    ) -> Result<FineTuningJobObject, APIError> {
465        self.get(&format!("fine_tuning/jobs/{}", req.fine_tuning_job_id))
466            .await
467    }
468
469    pub async fn cancel_fine_tuning_job(
470        &mut self,
471        req: CancelFineTuningJobRequest,
472    ) -> Result<FineTuningJobObject, APIError> {
473        self.post(
474            &format!("fine_tuning/jobs/{}/cancel", req.fine_tuning_job_id),
475            &req,
476        )
477        .await
478    }
479
480    pub async fn create_moderation(
481        &mut self,
482        req: CreateModerationRequest,
483    ) -> Result<CreateModerationResponse, APIError> {
484        self.post("moderations", &req).await
485    }
486
487    pub async fn create_assistant(
488        &mut self,
489        req: AssistantRequest,
490    ) -> Result<AssistantObject, APIError> {
491        self.post("assistants", &req).await
492    }
493
494    pub async fn retrieve_assistant(
495        &mut self,
496        assistant_id: String,
497    ) -> Result<AssistantObject, APIError> {
498        self.get(&format!("assistants/{assistant_id}")).await
499    }
500
501    pub async fn modify_assistant(
502        &mut self,
503        assistant_id: String,
504        req: AssistantRequest,
505    ) -> Result<AssistantObject, APIError> {
506        self.post(&format!("assistants/{assistant_id}"), &req).await
507    }
508
509    pub async fn delete_assistant(
510        &mut self,
511        assistant_id: String,
512    ) -> Result<common::DeletionStatus, APIError> {
513        self.delete(&format!("assistants/{assistant_id}")).await
514    }
515
516    pub async fn list_assistant(
517        &mut self,
518        limit: Option<i64>,
519        order: Option<String>,
520        after: Option<String>,
521        before: Option<String>,
522    ) -> Result<ListAssistant, APIError> {
523        let url = Self::query_params(limit, order, after, before, "assistants".to_string());
524        self.get(&url).await
525    }
526
527    pub async fn create_assistant_file(
528        &mut self,
529        assistant_id: String,
530        req: AssistantFileRequest,
531    ) -> Result<AssistantFileObject, APIError> {
532        self.post(&format!("assistants/{assistant_id}/files"), &req)
533            .await
534    }
535
536    pub async fn retrieve_assistant_file(
537        &mut self,
538        assistant_id: String,
539        file_id: String,
540    ) -> Result<AssistantFileObject, APIError> {
541        self.get(&format!("assistants/{assistant_id}/files/{file_id}"))
542            .await
543    }
544
545    pub async fn delete_assistant_file(
546        &mut self,
547        assistant_id: String,
548        file_id: String,
549    ) -> Result<common::DeletionStatus, APIError> {
550        self.delete(&format!("assistants/{assistant_id}/files/{file_id}"))
551            .await
552    }
553
554    pub async fn list_assistant_file(
555        &mut self,
556        assistant_id: String,
557        limit: Option<i64>,
558        order: Option<String>,
559        after: Option<String>,
560        before: Option<String>,
561    ) -> Result<ListAssistantFile, APIError> {
562        let url = Self::query_params(
563            limit,
564            order,
565            after,
566            before,
567            format!("assistants/{assistant_id}/files"),
568        );
569        self.get(&url).await
570    }
571
572    pub async fn create_thread(
573        &mut self,
574        req: CreateThreadRequest,
575    ) -> Result<ThreadObject, APIError> {
576        self.post("threads", &req).await
577    }
578
579    pub async fn retrieve_thread(&mut self, thread_id: String) -> Result<ThreadObject, APIError> {
580        self.get(&format!("threads/{thread_id}")).await
581    }
582
583    pub async fn modify_thread(
584        &mut self,
585        thread_id: String,
586        req: ModifyThreadRequest,
587    ) -> Result<ThreadObject, APIError> {
588        self.post(&format!("threads/{thread_id}"), &req).await
589    }
590
591    pub async fn delete_thread(
592        &mut self,
593        thread_id: String,
594    ) -> Result<common::DeletionStatus, APIError> {
595        self.delete(&format!("threads/{thread_id}")).await
596    }
597
598    pub async fn create_message(
599        &mut self,
600        thread_id: String,
601        req: CreateMessageRequest,
602    ) -> Result<MessageObject, APIError> {
603        self.post(&format!("threads/{thread_id}/messages"), &req)
604            .await
605    }
606
607    pub async fn retrieve_message(
608        &mut self,
609        thread_id: String,
610        message_id: String,
611    ) -> Result<MessageObject, APIError> {
612        self.get(&format!("threads/{thread_id}/messages/{message_id}"))
613            .await
614    }
615
616    pub async fn modify_message(
617        &mut self,
618        thread_id: String,
619        message_id: String,
620        req: ModifyMessageRequest,
621    ) -> Result<MessageObject, APIError> {
622        self.post(&format!("threads/{thread_id}/messages/{message_id}"), &req)
623            .await
624    }
625
626    pub async fn list_messages(&mut self, thread_id: String) -> Result<ListMessage, APIError> {
627        self.get(&format!("threads/{thread_id}/messages")).await
628    }
629
630    pub async fn retrieve_message_file(
631        &mut self,
632        thread_id: String,
633        message_id: String,
634        file_id: String,
635    ) -> Result<MessageFileObject, APIError> {
636        self.get(&format!(
637            "threads/{thread_id}/messages/{message_id}/files/{file_id}"
638        ))
639        .await
640    }
641
642    pub async fn list_message_file(
643        &mut self,
644        thread_id: String,
645        message_id: String,
646        limit: Option<i64>,
647        order: Option<String>,
648        after: Option<String>,
649        before: Option<String>,
650    ) -> Result<ListMessageFile, APIError> {
651        let url = Self::query_params(
652            limit,
653            order,
654            after,
655            before,
656            format!("threads/{thread_id}/messages/{message_id}/files"),
657        );
658        self.get(&url).await
659    }
660
661    pub async fn create_run(
662        &mut self,
663        thread_id: String,
664        req: CreateRunRequest,
665    ) -> Result<RunObject, APIError> {
666        self.post(&format!("threads/{thread_id}/runs"), &req).await
667    }
668
669    pub async fn retrieve_run(
670        &mut self,
671        thread_id: String,
672        run_id: String,
673    ) -> Result<RunObject, APIError> {
674        self.get(&format!("threads/{thread_id}/runs/{run_id}"))
675            .await
676    }
677
678    pub async fn modify_run(
679        &mut self,
680        thread_id: String,
681        run_id: String,
682        req: ModifyRunRequest,
683    ) -> Result<RunObject, APIError> {
684        self.post(&format!("threads/{thread_id}/runs/{run_id}"), &req)
685            .await
686    }
687
688    pub async fn list_run(
689        &mut self,
690        thread_id: String,
691        limit: Option<i64>,
692        order: Option<String>,
693        after: Option<String>,
694        before: Option<String>,
695    ) -> Result<ListRun, APIError> {
696        let url = Self::query_params(
697            limit,
698            order,
699            after,
700            before,
701            format!("threads/{thread_id}/runs"),
702        );
703        self.get(&url).await
704    }
705
706    pub async fn cancel_run(
707        &mut self,
708        thread_id: String,
709        run_id: String,
710    ) -> Result<RunObject, APIError> {
711        self.post(
712            &format!("threads/{thread_id}/runs/{run_id}/cancel"),
713            &ModifyRunRequest::default(),
714        )
715        .await
716    }
717
718    pub async fn create_thread_and_run(
719        &mut self,
720        req: CreateThreadAndRunRequest,
721    ) -> Result<RunObject, APIError> {
722        self.post("threads/runs", &req).await
723    }
724
725    pub async fn retrieve_run_step(
726        &mut self,
727        thread_id: String,
728        run_id: String,
729        step_id: String,
730    ) -> Result<RunStepObject, APIError> {
731        self.get(&format!(
732            "threads/{thread_id}/runs/{run_id}/steps/{step_id}"
733        ))
734        .await
735    }
736
737    pub async fn list_run_step(
738        &mut self,
739        thread_id: String,
740        run_id: String,
741        limit: Option<i64>,
742        order: Option<String>,
743        after: Option<String>,
744        before: Option<String>,
745    ) -> Result<ListRunStep, APIError> {
746        let url = Self::query_params(
747            limit,
748            order,
749            after,
750            before,
751            format!("threads/{thread_id}/runs/{run_id}/steps"),
752        );
753        self.get(&url).await
754    }
755
756    pub async fn create_batch(
757        &mut self,
758        req: CreateBatchRequest,
759    ) -> Result<BatchResponse, APIError> {
760        self.post("batches", &req).await
761    }
762
763    pub async fn retrieve_batch(&mut self, batch_id: String) -> Result<BatchResponse, APIError> {
764        self.get(&format!("batches/{batch_id}")).await
765    }
766
767    pub async fn cancel_batch(&mut self, batch_id: String) -> Result<BatchResponse, APIError> {
768        self.post(
769            &format!("batches/{batch_id}/cancel"),
770            &common::EmptyRequestBody {},
771        )
772        .await
773    }
774
775    pub async fn list_batch(
776        &mut self,
777        after: Option<String>,
778        limit: Option<i64>,
779    ) -> Result<ListBatchResponse, APIError> {
780        let url = Self::query_params(limit, None, after, None, "batches".to_string());
781        self.get(&url).await
782    }
783
784    pub async fn list_models(&mut self) -> Result<ModelsResponse, APIError> {
785        self.get("models").await
786    }
787
788    pub async fn retrieve_model(&mut self, model_id: String) -> Result<ModelResponse, APIError> {
789        self.get(&format!("models/{model_id}")).await
790    }
791
792    pub async fn delete_model(
793        &mut self,
794        model_id: String,
795    ) -> Result<common::DeletionStatus, APIError> {
796        self.delete(&format!("models/{model_id}")).await
797    }
798
799    fn build_url_with_preserved_query(&self, path: &str) -> Result<String, url::ParseError> {
800        let (base, query_opt) = match self.api_endpoint.split_once('?') {
801            Some((b, q)) => (b.trim_end_matches('/'), Some(q)),
802            None => (self.api_endpoint.trim_end_matches('/'), None),
803        };
804
805        let full_path = format!("{}/{}", base, path.trim_start_matches('/'));
806        let mut url = Url::parse(&full_path)?;
807
808        if let Some(query) = query_opt {
809            for (k, v) in url::form_urlencoded::parse(query.as_bytes()) {
810                url.query_pairs_mut().append_pair(&k, &v);
811            }
812        }
813        Ok(url.to_string())
814    }
815
816    fn query_params(
817        limit: Option<i64>,
818        order: Option<String>,
819        after: Option<String>,
820        before: Option<String>,
821        mut url: String,
822    ) -> String {
823        let mut params = vec![];
824        if let Some(limit) = limit {
825            params.push(format!("limit={limit}"));
826        }
827        if let Some(order) = order {
828            params.push(format!("order={order}"));
829        }
830        if let Some(after) = after {
831            params.push(format!("after={after}"));
832        }
833        if let Some(before) = before {
834            params.push(format!("before={before}"));
835        }
836        if !params.is_empty() {
837            url = format!("{}?{}", url, params.join("&"));
838        }
839        url
840    }
841
842    fn is_beta(path: &str) -> bool {
843        path.starts_with("assistants") || path.starts_with("threads")
844    }
845
846    fn create_form<T>(req: &T, file_field: &str) -> Result<Form, APIError>
847    where
848        T: Serialize,
849    {
850        let json = match serde_json::to_value(req) {
851            Ok(json) => json,
852            Err(e) => {
853                return Err(APIError::CustomError {
854                    message: e.to_string(),
855                })
856            }
857        };
858        let file_path = if let Value::Object(map) = &json {
859            map.get(file_field)
860                .and_then(|v| v.as_str())
861                .ok_or(APIError::CustomError {
862                    message: format!("Field '{file_field}' not found or not a string"),
863                })?
864        } else {
865            return Err(APIError::CustomError {
866                message: "Request is not a JSON object".to_string(),
867            });
868        };
869
870        let mut file = match File::open(file_path) {
871            Ok(file) => file,
872            Err(e) => {
873                return Err(APIError::CustomError {
874                    message: e.to_string(),
875                })
876            }
877        };
878        let mut buffer = Vec::new();
879        match file.read_to_end(&mut buffer) {
880            Ok(_) => {}
881            Err(e) => {
882                return Err(APIError::CustomError {
883                    message: e.to_string(),
884                })
885            }
886        }
887
888        let mut form =
889            Form::new().part("file", Part::bytes(buffer).file_name(file_path.to_string()));
890
891        if let Value::Object(map) = json {
892            for (key, value) in map.into_iter() {
893                if key != file_field {
894                    match value {
895                        Value::String(s) => {
896                            form = form.text(key, s);
897                        }
898                        Value::Number(n) => {
899                            form = form.text(key, n.to_string());
900                        }
901                        _ => {}
902                    }
903                }
904            }
905        }
906
907        Ok(form)
908    }
909
910    fn create_form_from_bytes<T>(req: &T, bytes: Vec<u8>) -> Result<Form, APIError>
911    where
912        T: Serialize,
913    {
914        let json = match serde_json::to_value(req) {
915            Ok(json) => json,
916            Err(e) => {
917                return Err(APIError::CustomError {
918                    message: e.to_string(),
919                })
920            }
921        };
922
923        let mut form = Form::new().part("file", Part::bytes(bytes.clone()).file_name("file.mp3"));
924
925        if let Value::Object(map) = json {
926            for (key, value) in map.into_iter() {
927                match value {
928                    Value::String(s) => {
929                        form = form.text(key, s);
930                    }
931                    Value::Number(n) => {
932                        form = form.text(key, n.to_string());
933                    }
934                    _ => {}
935                }
936            }
937        }
938
939        Ok(form)
940    }
941}