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