1use crate::{
2 config::{config_methods, Config, ConfigBuilder},
3 transport, Error, ListModelsResponse, RawResponse, RequestOptions, SystemOneRequest,
4 SystemOneResponse,
5};
6use reqwest::Method;
7use serde_json::Value;
8use std::time::Instant;
9
10#[derive(Clone)]
12pub struct Client {
13 config: Config,
14 http: reqwest::Client,
15}
16
17#[derive(Default)]
19pub struct ClientBuilder {
20 config: ConfigBuilder,
21 http: Option<reqwest::Client>,
22}
23
24impl std::fmt::Debug for Client {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 f.debug_struct("Client")
27 .field("config", &self.config)
28 .finish_non_exhaustive()
29 }
30}
31
32impl std::fmt::Debug for ClientBuilder {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 f.debug_struct("ClientBuilder").finish_non_exhaustive()
35 }
36}
37
38impl ClientBuilder {
39 config_methods!();
40 pub fn http_client(mut self, client: reqwest::Client) -> Self {
43 self.http = Some(client);
44 self
45 }
46 pub fn build(self) -> Result<Client, Error> {
48 let config = self.config.resolve()?;
49 let http = match self.http {
50 Some(http) => http,
51 None => reqwest::Client::builder()
52 .redirect(reqwest::redirect::Policy::none())
53 .build()
54 .map_err(Error::Connection)?,
55 };
56 Ok(Client { config, http })
57 }
58}
59
60impl Client {
61 pub fn new() -> Result<Self, Error> {
63 Self::builder().build()
64 }
65 pub fn builder() -> ClientBuilder {
67 ClientBuilder::default()
68 }
69 pub fn default_model(&self) -> &str {
71 &self.config.model
72 }
73 pub fn base_url(&self) -> &str {
75 &self.config.base_url
76 }
77 pub async fn system_one(&self, request: SystemOneRequest) -> Result<SystemOneResponse, Error> {
79 self.system_one_with_options(request, RequestOptions::default())
80 .await
81 }
82 pub async fn system_one_with_options(
84 &self,
85 request: SystemOneRequest,
86 options: RequestOptions,
87 ) -> Result<SystemOneResponse, Error> {
88 let body = request.prepare(&self.config.model)?;
89 self.send(Method::POST, "/v1/systemone", Some(body), options, |raw| {
90 SystemOneResponse::from_raw_with_log_level(raw, self.config.log_level)
91 })
92 .await
93 }
94 pub fn models(&self) -> Models<'_> {
96 Models(self)
97 }
98
99 async fn send<T>(
100 &self,
101 method: Method,
102 path: &str,
103 body: Option<Value>,
104 options: RequestOptions,
105 decode: impl Fn(RawResponse) -> Result<T, Error>,
106 ) -> Result<T, Error> {
107 let request = transport::prepare(&self.config, method, path, body, &options)?;
108 let started = Instant::now();
109 let mut attempt = 0;
110 loop {
111 let headers = request.headers(attempt);
112 let attempt_started = Instant::now();
113 request.log_request(&self.config, &headers, attempt);
114 let mut builder = self
115 .http
116 .request(request.method.clone(), &request.url)
117 .headers(headers)
118 .timeout(request.timeout);
119 if let Some(body) = &request.body {
120 builder = builder.body(body.clone());
121 }
122 let operation = async {
123 let response = builder.send().await.map_err(|e| request.map_error(e))?;
124 let status = response.status();
125 let headers = response.headers().clone();
126 let body = response.bytes().await.map_err(|e| request.map_error(e))?;
127 let raw = RawResponse {
128 status,
129 headers,
130 body,
131 };
132 request.log_response(&self.config, &raw, attempt_started);
133 request.finish(raw, &decode)
134 };
135 let result = tokio::select! {
136 biased;
137 _ = cancelled(&options) => return Err(Error::Cancelled),
138 result = operation => result,
139 };
140 match result {
141 Ok(response) => return Ok(response),
142 Err(error) => {
143 let Some(delay) = request.retry_delay(&error, attempt, started) else {
144 return Err(error);
145 };
146 if self.config.log_level >= log::LevelFilter::Info {
147 log::info!(target: "typesafe_ai_rs", "retry={} delay_ms={}", attempt + 1, delay.as_millis());
148 }
149 tokio::select! {
150 biased;
151 _ = cancelled(&options) => return Err(Error::Cancelled),
152 _ = tokio::time::sleep(delay) => {},
153 }
154 attempt += 1;
155 }
156 }
157 }
158 }
159}
160
161async fn cancelled(options: &RequestOptions) {
162 match &options.cancellation_token {
163 Some(token) => token.cancelled().await,
164 None => std::future::pending::<()>().await,
165 }
166}
167
168#[derive(Clone, Copy, Debug)]
170pub struct Models<'a>(&'a Client);
171impl Models<'_> {
172 pub async fn list(&self) -> Result<ListModelsResponse, Error> {
174 self.list_with_options(RequestOptions::default()).await
175 }
176 pub async fn list_with_options(
178 &self,
179 options: RequestOptions,
180 ) -> Result<ListModelsResponse, Error> {
181 self.0
182 .send(
183 Method::GET,
184 "/v1/models",
185 None,
186 options,
187 ListModelsResponse::from_raw,
188 )
189 .await
190 }
191}