openai_rst/
client.rs

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