1use std::collections::HashMap;
4use std::sync::{Arc, OnceLock};
5use std::time::Duration;
6
7use async_openai::config::OpenAIConfig;
8use async_openai::Client as OpenAIClient;
9
10use crate::constants::{DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT};
11use crate::error::Result;
12use crate::http::{HttpConfig, HttpTransport};
13use crate::openai_delegate::new_openai_delegate;
14use crate::options::FloopyOptions;
15use crate::resources::{
16 Constraints, Decisions, Evaluations, Experiments, Export, Feedback, Routing, Sessions,
17};
18
19#[derive(Clone)]
25pub struct Floopy {
26 transport: Arc<HttpTransport>,
27 openai: Arc<OnceLock<OpenAIClient<OpenAIConfig>>>,
28}
29
30impl Floopy {
31 pub fn new(api_key: impl Into<String>) -> Result<Self> {
38 Self::builder(api_key).build()
39 }
40
41 #[must_use]
44 pub fn builder(api_key: impl Into<String>) -> FloopyBuilder {
45 FloopyBuilder::new(api_key)
46 }
47
48 #[must_use]
56 pub fn openai(&self) -> &OpenAIClient<OpenAIConfig> {
57 self.openai.get_or_init(|| {
58 new_openai_delegate(&self.transport)
59 .expect("delegate construction with SDK-produced headers is infallible")
60 })
61 }
62
63 #[must_use]
65 pub fn base_url(&self) -> &str {
66 self.transport.base_url()
67 }
68
69 #[must_use]
71 pub fn feedback(&self) -> Feedback {
72 Feedback::new(self.transport.clone())
73 }
74
75 #[must_use]
77 pub fn decisions(&self) -> Decisions {
78 Decisions::new(self.transport.clone())
79 }
80
81 #[must_use]
83 pub fn experiments(&self) -> Experiments {
84 Experiments::new(self.transport.clone())
85 }
86
87 #[must_use]
89 pub fn constraints(&self) -> Constraints {
90 Constraints::new(self.transport.clone())
91 }
92
93 #[must_use]
95 pub fn export(&self) -> Export {
96 Export::new(self.transport.clone())
97 }
98
99 #[must_use]
101 pub fn evaluations(&self) -> Evaluations {
102 Evaluations::new(self.transport.clone())
103 }
104
105 #[must_use]
107 pub fn routing(&self) -> Routing {
108 Routing::new(self.transport.clone())
109 }
110
111 #[must_use]
113 pub fn sessions(&self) -> Sessions {
114 Sessions::new(self.transport.clone())
115 }
116}
117
118impl std::fmt::Debug for Floopy {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 f.debug_struct("Floopy")
122 .field("base_url", &self.transport.base_url())
123 .finish_non_exhaustive()
124 }
125}
126
127pub struct FloopyBuilder {
129 api_key: String,
130 base_url: String,
131 timeout: Duration,
132 max_retries: u32,
133 default_headers: HashMap<String, String>,
134 options: Option<FloopyOptions>,
135 http_client: Option<reqwest::Client>,
136}
137
138impl FloopyBuilder {
139 fn new(api_key: impl Into<String>) -> Self {
140 Self {
141 api_key: api_key.into(),
142 base_url: DEFAULT_BASE_URL.to_owned(),
143 timeout: DEFAULT_TIMEOUT,
144 max_retries: DEFAULT_MAX_RETRIES,
145 default_headers: HashMap::new(),
146 options: None,
147 http_client: None,
148 }
149 }
150
151 #[must_use]
156 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
157 self.base_url = base_url.into();
158 self
159 }
160
161 #[must_use]
163 pub fn timeout(mut self, timeout: Duration) -> Self {
164 self.timeout = timeout;
165 self
166 }
167
168 #[must_use]
170 pub fn max_retries(mut self, max_retries: u32) -> Self {
171 self.max_retries = max_retries;
172 self
173 }
174
175 #[must_use]
177 pub fn default_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
178 self.default_headers.insert(key.into(), value.into());
179 self
180 }
181
182 #[must_use]
184 pub fn options(mut self, options: FloopyOptions) -> Self {
185 self.options = Some(options);
186 self
187 }
188
189 #[must_use]
191 pub fn http_client(mut self, client: reqwest::Client) -> Self {
192 self.http_client = Some(client);
193 self
194 }
195
196 pub fn build(self) -> Result<Floopy> {
203 let transport = HttpTransport::new(HttpConfig {
204 api_key: self.api_key,
205 base_url: self.base_url,
206 timeout: self.timeout,
207 max_retries: self.max_retries,
208 default_headers: self.default_headers,
209 default_options: self.options,
210 http_client: self.http_client,
211 })?;
212 Ok(Floopy {
213 transport: Arc::new(transport),
214 openai: Arc::new(OnceLock::new()),
215 })
216 }
217}