openai_agents_rust/config/
mod.rs

1use crate::error::AgentError;
2use crate::utils::env::var_nonempty;
3use config::{Config as ConfigLoader, Environment, File};
4use std::path::Path;
5
6mod schema;
7pub use schema::Config;
8
9/// Load configuration from a YAML or JSON file and merge with environment variables.
10///
11/// The function reads the file at `path`, then overlays any environment variables
12/// prefixed with `OPENAI_AGENTS_`. Environment variable names are converted to
13/// lower‑case and underscores are replaced with dots to match the struct fields.
14///
15/// # Errors
16///
17/// Returns `AgentError` if the file cannot be read, parsed, or if required fields
18/// are missing.
19pub fn load_from_path<P: AsRef<Path>>(path: P) -> Result<Config, AgentError> {
20    // Build a config loader using the builder API (the `merge` method was removed in recent versions).
21    let builder = ConfigLoader::builder()
22        .add_source(
23            File::with_name(
24                path.as_ref()
25                    .to_str()
26                    .ok_or_else(|| AgentError::Other("Invalid config file path".to_string()))?,
27            )
28            .required(false),
29        )
30        // Use "__" as separator so single underscores remain intact (e.g., BASE_URL -> base_url)
31        .add_source(Environment::with_prefix("OPENAI_AGENTS").separator("__"));
32
33    // Build the configuration and deserialize into our strongly‑typed `Config` struct.
34    let settings = builder
35        .build()
36        .map_err(|e| AgentError::Other(format!("Config build error: {}", e)))?;
37    let mut cfg = settings
38        .try_deserialize::<Config>()
39        .map_err(|e| AgentError::Other(format!("Config error: {}", e)))?;
40    apply_env_overrides(&mut cfg);
41    Ok(cfg)
42}
43
44/// Load configuration purely from environment variables (after .env is loaded by the caller).
45/// Priority:
46/// - OPENAI_MODEL, OPENAI_API_KEY, OPENAI_BASE_URL, RUST_LOG
47///
48/// Note: No provider defaults are applied here. Missing variables remain empty
49/// and should be handled by callers or higher-level config files.
50pub fn load_from_env() -> Config {
51    let mut cfg = Config {
52        api_key: String::new(),
53        model: var_nonempty("OPENAI_MODEL").unwrap_or_default(),
54        base_url: var_nonempty("OPENAI_BASE_URL").unwrap_or_default(),
55        log_level: std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
56        plugins_path: schema::default_plugins_path(),
57        max_concurrent_requests: None,
58    };
59    if let Some(k) = var_nonempty("OPENAI_API_KEY") {
60        cfg.api_key = k;
61    }
62    apply_env_overrides(&mut cfg);
63    cfg
64}
65
66/// Apply generic provider environment overrides on top of a loaded Config.
67fn apply_env_overrides(cfg: &mut Config) {
68    if let Some(k) = var_nonempty("OPENAI_API_KEY") {
69        cfg.api_key = k;
70    }
71    if let Some(u) = var_nonempty("OPENAI_BASE_URL") {
72        cfg.base_url = u;
73    }
74    if let Some(m) = var_nonempty("OPENAI_MODEL") {
75        cfg.model = m;
76    }
77    if let Ok(lvl) = std::env::var("RUST_LOG") {
78        if !lvl.trim().is_empty() {
79            cfg.log_level = lvl;
80        }
81    }
82}