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;