Skip to main content

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}