synth_ai_core/api/client.rs
1//! Main Synth API client.
2//!
3//! The `SynthClient` is the primary entry point for interacting with the Synth API.
4
5use crate::auth;
6use crate::http::HttpClient;
7use crate::urls::backend_url_base;
8use crate::CoreError;
9
10use super::eval::EvalClient;
11use super::graph_evolve::GraphEvolveClient;
12use super::graphs::GraphsClient;
13use super::inference::InferenceClient;
14use super::jobs::JobsClient;
15use super::container::ContainerDeployClient;
16
17/// Default backend URL.
18pub const DEFAULT_BACKEND_URL: &str = crate::urls::DEFAULT_BACKEND_URL;
19
20/// Default request timeout in seconds.
21pub const DEFAULT_TIMEOUT_SECS: u64 = 120;
22
23/// Synth API client.
24///
25/// This is the main entry point for interacting with the Synth API.
26/// It provides access to sub-clients for different API endpoints.
27///
28/// # Example
29///
30/// ```ignore
31/// use synth_ai_core::api::SynthClient;
32///
33/// // Create from environment variable
34/// let client = SynthClient::from_env()?;
35///
36/// // Or with explicit API key
37/// let client = SynthClient::new("sk_live_...", None)?;
38///
39/// // Access sub-clients
40/// let jobs = client.jobs();
41/// let eval = client.eval();
42/// let graphs = client.graphs();
43/// ```
44pub struct SynthClient {
45 pub(crate) http: HttpClient,
46 pub(crate) base_url: String,
47}
48
49impl SynthClient {
50 /// Create a new Synth client with an API key.
51 ///
52 /// # Arguments
53 ///
54 /// * `api_key` - Your Synth API key
55 /// * `base_url` - Optional base URL (defaults to https://api.usesynth.ai)
56 ///
57 /// # Example
58 ///
59 /// ```ignore
60 /// let client = SynthClient::new("sk_live_...", None)?;
61 /// ```
62 pub fn new(api_key: &str, base_url: Option<&str>) -> Result<Self, CoreError> {
63 let base_url = base_url
64 .map(|url| url.to_string())
65 .unwrap_or_else(backend_url_base);
66 let http = HttpClient::new(&base_url, api_key, DEFAULT_TIMEOUT_SECS)
67 .map_err(|e| CoreError::Internal(format!("failed to create HTTP client: {}", e)))?;
68
69 Ok(Self { http, base_url })
70 }
71
72 /// Create a new Synth client with custom timeout.
73 ///
74 /// # Arguments
75 ///
76 /// * `api_key` - Your Synth API key
77 /// * `base_url` - Optional base URL
78 /// * `timeout_secs` - Request timeout in seconds
79 pub fn with_timeout(
80 api_key: &str,
81 base_url: Option<&str>,
82 timeout_secs: u64,
83 ) -> Result<Self, CoreError> {
84 let base_url = base_url
85 .map(|url| url.to_string())
86 .unwrap_or_else(backend_url_base);
87 let http = HttpClient::new(&base_url, api_key, timeout_secs)
88 .map_err(|e| CoreError::Internal(format!("failed to create HTTP client: {}", e)))?;
89
90 Ok(Self { http, base_url })
91 }
92
93 /// Create a client from environment variables.
94 ///
95 /// Reads the API key from `SYNTH_API_KEY` environment variable.
96 /// Optionally reads base URL from `SYNTH_BACKEND_URL`.
97 ///
98 /// # Example
99 ///
100 /// ```ignore
101 /// std::env::set_var("SYNTH_API_KEY", "sk_live_...");
102 /// let client = SynthClient::from_env()?;
103 /// ```
104 pub fn from_env() -> Result<Self, CoreError> {
105 let api_key = auth::get_api_key(None).ok_or_else(|| {
106 CoreError::Authentication("SYNTH_API_KEY environment variable not set".to_string())
107 })?;
108
109 let base_url = std::env::var("SYNTH_BACKEND_URL").ok();
110 Self::new(&api_key, base_url.as_deref())
111 }
112
113 /// Create a client, minting a demo key if needed.
114 ///
115 /// This will:
116 /// 1. Try to get an API key from environment
117 /// 2. If not found and `allow_mint` is true, mint a demo key
118 ///
119 /// # Arguments
120 ///
121 /// * `allow_mint` - Whether to mint a demo key if no key is found
122 /// * `base_url` - Optional base URL
123 pub async fn from_env_or_mint(
124 allow_mint: bool,
125 base_url: Option<&str>,
126 ) -> Result<Self, CoreError> {
127 let api_key = auth::get_or_mint_api_key(base_url, allow_mint).await?;
128 Self::new(&api_key, base_url)
129 }
130
131 /// Get the base URL for this client.
132 pub fn base_url(&self) -> &str {
133 &self.base_url
134 }
135
136 /// Get a reference to the HTTP client.
137 pub fn http(&self) -> &HttpClient {
138 &self.http
139 }
140
141 /// Get a Jobs API client.
142 ///
143 /// Use this to submit, poll, and cancel optimization jobs.
144 ///
145 /// # Example
146 ///
147 /// ```ignore
148 /// let job_id = client.jobs().submit_gepa(request).await?;
149 /// let result = client.jobs().poll_until_complete(&job_id, 3600.0, 15.0).await?;
150 /// ```
151 pub fn jobs(&self) -> JobsClient<'_> {
152 JobsClient::new(self)
153 }
154
155 /// Get an Eval API client.
156 ///
157 /// Use this to run evaluation jobs.
158 ///
159 /// # Example
160 ///
161 /// ```ignore
162 /// let job_id = client.eval().submit(request).await?;
163 /// let result = client.eval().poll_until_complete(&job_id, 3600.0, 15.0).await?;
164 /// ```
165 pub fn eval(&self) -> EvalClient<'_> {
166 EvalClient::new(self)
167 }
168
169 /// Get a Graphs API client.
170 ///
171 /// Use this for graph completions and verifier inference.
172 ///
173 /// # Example
174 ///
175 /// ```ignore
176 /// let result = client.graphs().verify(trace, rubric, None).await?;
177 /// ```
178 pub fn graphs(&self) -> GraphsClient<'_> {
179 GraphsClient::new(self)
180 }
181
182 /// Get a Graph Evolve API client.
183 ///
184 /// Use this for Graph Evolve / GraphGen optimization endpoints.
185 pub fn graph_evolve(&self) -> GraphEvolveClient<'_> {
186 GraphEvolveClient::new(self)
187 }
188
189 /// Get an Inference API client.
190 ///
191 /// Use this for chat completions via the inference proxy.
192 pub fn inference(&self) -> InferenceClient<'_> {
193 InferenceClient::new(&self.http)
194 }
195
196 /// Get a Container Deployments client.
197 ///
198 /// Use this to deploy and manage managed Container deployments.
199 pub fn container(&self) -> ContainerDeployClient<'_> {
200 ContainerDeployClient::new(self)
201 }
202}
203
204impl std::fmt::Debug for SynthClient {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 f.debug_struct("SynthClient")
207 .field("base_url", &self.base_url)
208 .finish_non_exhaustive()
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_default_backend_url() {
218 assert_eq!(DEFAULT_BACKEND_URL, crate::urls::DEFAULT_BACKEND_URL);
219 }
220}