1pub extern crate futures_util;
2use anyhow::{anyhow, Result};
3use lazy_static::lazy_static;
4use std::time::Duration;
5
6lazy_static! {
7 static ref DEFAULT_BASE_URL: reqwest::Url =
8 reqwest::Url::parse("https://api.openai.com/v1/models").unwrap();
9
10 static ref SHARED_HTTP_CLIENT: reqwest::Client = {
20 reqwest::ClientBuilder::new()
21 .pool_idle_timeout(Some(Duration::from_secs(90)))
22 .pool_max_idle_per_host(100)
23 .tcp_keepalive(Some(Duration::from_secs(60)))
24 .timeout(Duration::from_secs(300))
25 .connect_timeout(Duration::from_secs(10))
26 .tcp_nodelay(true)
27 .build()
28 .expect("Failed to build shared HTTP client")
29 };
30}
31
32pub struct Client {
33 req_client: reqwest::Client,
34 key: String,
35 base_url: reqwest::Url,
36}
37
38pub mod chat;
39pub mod completions;
40pub mod edits;
41pub mod embeddings;
42pub mod images;
43pub mod models;
44
45impl Client {
46 pub fn new(api_key: &str) -> Client {
49 Client {
50 req_client: SHARED_HTTP_CLIENT.clone(),
51 key: api_key.to_owned(),
52 base_url: DEFAULT_BASE_URL.clone(),
53 }
54 }
55
56 pub fn new_with_client(api_key: &str, req_client: reqwest::Client) -> Client {
59 Client {
60 req_client,
61 key: api_key.to_owned(),
62 base_url: DEFAULT_BASE_URL.clone(),
63 }
64 }
65
66 pub fn new_with_base_url(api_key: &str, base_url: &str) -> Client {
68 let base_url = reqwest::Url::parse(base_url).unwrap();
69 Client {
70 req_client: SHARED_HTTP_CLIENT.clone(),
71 key: api_key.to_owned(),
72 base_url,
73 }
74 }
75
76 pub fn new_with_client_and_base_url(
78 api_key: &str,
79 req_client: reqwest::Client,
80 base_url: &str,
81 ) -> Client {
82 Client {
83 req_client,
84 key: api_key.to_owned(),
85 base_url: reqwest::Url::parse(base_url).unwrap(),
86 }
87 }
88
89 pub fn shared_client() -> &'static reqwest::Client {
91 &SHARED_HTTP_CLIENT
92 }
93
94 async fn send_json<T: serde::de::DeserializeOwned>(
97 &self,
98 path: &str,
99 body: &impl serde::Serialize,
100 error_context: &str,
101 ) -> Result<T, anyhow::Error> {
102 let mut url = self.base_url.clone();
103 url.set_path(path);
104
105 let res = self
106 .req_client
107 .post(url)
108 .bearer_auth(&self.key)
109 .json(body)
110 .send()
111 .await?;
112
113 let status = res.status();
114 let text = res.text().await?;
115
116 if status == 200 {
117 serde_json::from_str(&text)
118 .map_err(|e| anyhow!("{} failed to parse: {}. Raw: {}", error_context, e, text))
119 } else {
120 Err(anyhow!(
121 "{} API error ({}): {}",
122 error_context,
123 status,
124 text
125 ))
126 }
127 }
128
129 async fn send_get<T: serde::de::DeserializeOwned>(
131 &self,
132 path: &str,
133 error_context: &str,
134 ) -> Result<T, anyhow::Error> {
135 let mut url = self.base_url.clone();
136 url.set_path(path);
137
138 let res = self
139 .req_client
140 .get(url)
141 .bearer_auth(&self.key)
142 .send()
143 .await?;
144
145 let status = res.status();
146 let text = res.text().await?;
147
148 if status == 200 {
149 serde_json::from_str(&text)
150 .map_err(|e| anyhow!("{} failed to parse: {}. Raw: {}", error_context, e, text))
151 } else {
152 Err(anyhow!(
153 "{} API error ({}): {}",
154 error_context,
155 status,
156 text
157 ))
158 }
159 }
160
161 pub async fn list_models(
162 &self,
163 opt_url_path: Option<String>,
164 ) -> Result<Vec<models::Model>, anyhow::Error> {
165 let path = opt_url_path.unwrap_or_else(|| String::from("/v1/models"));
166 #[derive(serde::Deserialize)]
167 struct ListModelsResponse {
168 data: Vec<models::Model>,
169 }
170 let response: ListModelsResponse = self.send_get(&path, "list_models").await?;
171 Ok(response.data)
172 }
173
174 pub async fn create_chat(
175 &self,
176 args: chat::ChatArguments,
177 opt_url_path: Option<String>,
178 ) -> Result<chat::ChatCompletion, anyhow::Error> {
179 let path = opt_url_path.unwrap_or_else(|| String::from("/v1/chat/completions"));
180 self.send_json(&path, &args, "create_chat").await
181 }
182
183 pub async fn create_chat_stream(
184 &self,
185 args: chat::ChatArguments,
186 opt_url_path: Option<String>,
187 ) -> Result<chat::stream::ChatCompletionChunkStream> {
188 let mut url = self.base_url.clone();
189 url.set_path(&opt_url_path.unwrap_or_else(|| String::from("/v1/chat/completions")));
190
191 let mut args = args;
192 args.stream = Some(true);
193
194 let res = self
195 .req_client
196 .post(url)
197 .bearer_auth(&self.key)
198 .json(&args)
199 .send()
200 .await?;
201
202 if res.status() == 200 {
203 Ok(chat::stream::ChatCompletionChunkStream::new(Box::pin(
204 res.bytes_stream(),
205 )))
206 } else {
207 Err(anyhow!(res.text().await?))
208 }
209 }
210
211 pub async fn create_completion(
212 &self,
213 args: completions::CompletionArguments,
214 opt_url_path: Option<String>,
215 ) -> Result<completions::CompletionResponse> {
216 let path = opt_url_path.unwrap_or_else(|| String::from("/v1/completions"));
217 self.send_json(&path, &args, "create_completion").await
218 }
219
220 pub async fn create_embeddings(
221 &self,
222 args: embeddings::EmbeddingsArguments,
223 opt_url_path: Option<String>,
224 ) -> Result<embeddings::EmbeddingsResponse> {
225 let path = opt_url_path.unwrap_or_else(|| String::from("/v1/embeddings"));
226 self.send_json(&path, &args, "create_embeddings").await
227 }
228
229 pub async fn create_image_old(
230 &self,
231 args: images::ImageArguments,
232 opt_url_path: Option<String>,
233 ) -> Result<Vec<String>> {
234 let path = opt_url_path.unwrap_or_else(|| String::from("/v1/images/generations"));
235 let response: images::ImageResponse =
236 self.send_json(&path, &args, "create_image_old").await?;
237 Ok(response
238 .data
239 .iter()
240 .map(|o| match o {
241 images::ImageObject::Url(s) => s.to_string(),
242 images::ImageObject::Base64JSON(s) => s.to_string(),
243 })
244 .collect())
245 }
246
247 pub async fn create_image(
248 &self,
249 args: images::ImageArguments,
250 opt_url_path: Option<String>,
251 ) -> Result<Vec<String>> {
252 let path = opt_url_path.unwrap_or_else(|| String::from("/v1/images/generations"));
253 let image_args = images::ImageArguments {
254 prompt: args.prompt,
255 model: Some("gpt-image-1".to_string()),
256 n: Some(1),
257 size: Some("1024x1024".to_string()),
258 quality: Some("auto".to_string()),
259 user: None,
260 };
261 let response: images::ImageResponse =
262 self.send_json(&path, &image_args, "create_image").await?;
263 Ok(response
264 .data
265 .iter()
266 .map(|o| match o {
267 images::ImageObject::Url(s) => s.to_string(),
268 images::ImageObject::Base64JSON(s) => s.to_string(),
269 })
270 .collect())
271 }
272
273 pub async fn create_responses(
303 &self,
304 args: chat::ResponsesArguments,
305 opt_url_path: Option<String>,
306 ) -> Result<chat::ResponsesCompletion, anyhow::Error> {
307 let path = opt_url_path.unwrap_or_else(|| String::from("/v1/responses"));
308 self.send_json(&path, &args, "create_responses").await
309 }
310
311 pub async fn create_openai_responses(
343 &self,
344 args: chat::OpenAIResponsesArguments,
345 opt_url_path: Option<String>,
346 ) -> Result<chat::ResponsesCompletion, anyhow::Error> {
347 let path = opt_url_path.unwrap_or_else(|| String::from("/v1/responses"));
348 self.send_json(&path, &args, "create_openai_responses")
349 .await
350 }
351}