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::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}