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