Skip to main content

artificial_openai/
adapter.rs

1use std::{env, sync::Arc};
2
3use artificial_core::error::{ArtificialError, Result};
4
5use crate::client::{HttpTimeoutConfig, OpenAiClient, RetryPolicy};
6
7/// Thin wrapper that wires the HTTP client [`OpenAiClient`] into a value that
8/// implements [`artificial_core::backend::Backend`].
9///
10/// Think of it as the **service locator** for the OpenAI back-end:
11///
12/// * stores the API key (and optionally a custom base URL in the future),
13/// * owns a shareable, connection-pooled `reqwest::Client`,
14/// * provides a fluent [`OpenAiAdapterBuilder`] so callers don’t have to juggle
15///   `Option<String>` manually.
16///
17/// The type itself purposefully exposes **no additional methods**—all user-
18/// facing functionality sits on the generic [`artificial_core::ArtificialClient`]
19/// once the adapter is plugged in.
20pub struct OpenAiAdapter {
21    pub(crate) client: Arc<OpenAiClient>,
22}
23
24impl OpenAiAdapter {}
25
26/// Builder-style configuration for constructing [`OpenAiAdapter`].
27///
28/// # Typical usage
29///
30/// ```rust,no_run
31/// use artificial_openai::OpenAiAdapterOptions;
32///
33/// let backend = OpenAiAdapterOptions::new_from_env()
34///     .build()
35///     .expect("OPENAI_API_KEY must be set");
36/// ```
37///
38/// The builder pattern keeps future options (proxy URL, organisation ID, …)
39/// backwards compatible without breaking existing `build()` calls.
40#[derive(Default)]
41pub struct OpenAiAdapterOptions {
42    pub(crate) api_key: Option<String>,
43    pub(crate) retry: Option<RetryPolicy>,
44    pub(crate) timeouts: Option<HttpTimeoutConfig>,
45}
46
47impl OpenAiAdapterOptions {
48    /// Create an *empty* builder. Remember to supply an API key manually.
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    /// Convenience constructor that tries to load the `OPENAI_API_KEY`
54    /// environment variable.
55    ///
56    /// # Panics
57    ///
58    /// Never panics. Missing keys only surface during [`Self::build`].
59    pub fn new_from_env() -> Self {
60        Self {
61            api_key: env::var("OPENAI_API_KEY").ok(),
62            retry: None,
63            timeouts: None,
64        }
65    }
66
67    /// Set API key explicitly.
68    pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
69        self.api_key = Some(api_key.into());
70        self
71    }
72
73    /// Set a retry policy for OpenAI HTTP calls.
74    pub fn with_retry_policy(mut self, retry: RetryPolicy) -> Self {
75        self.retry = Some(retry);
76        self
77    }
78
79    /// Set HTTP timeout configuration for upstream requests.
80    pub fn with_http_timeouts(mut self, timeouts: HttpTimeoutConfig) -> Self {
81        self.timeouts = Some(timeouts);
82        self
83    }
84
85    /// Finalise the builder and return a ready-to-use adapter.
86    ///
87    /// # Errors
88    ///
89    /// * [`ArtificialError::Invalid`] – if the API key is missing.
90    pub fn build(self) -> Result<OpenAiAdapter> {
91        let api_key = self.api_key.ok_or(ArtificialError::Invalid(
92            "missing env variable: `OPENAI_API_KEY`".into(),
93        ))?;
94
95        let mut client = if let Some(timeouts) = self.timeouts {
96            OpenAiClient::new_with_timeouts(api_key, timeouts)
97        } else {
98            OpenAiClient::new(api_key)
99        };
100        if let Some(retry) = self.retry {
101            client = client.with_retry_policy(retry);
102        }
103
104        Ok(OpenAiAdapter {
105            client: Arc::new(client),
106        })
107    }
108}
109
110/// Backwards-compatible alias kept for existing code.
111pub type OpenAiAdapterBuilder = OpenAiAdapterOptions;