use std::fmt;
use std::path::PathBuf;
use super::Options;
use crate::wire;
pub const AGENTCAT_PROJECT_ID: &str = "AGENTCAT_PROJECT_ID";
pub const AGENTCAT_API_URL: &str = "AGENTCAT_API_URL";
pub const MCPCAT_API_URL: &str = "MCPCAT_API_URL";
pub const AGENTCAT_ENABLED: &str = "AGENTCAT_ENABLED";
pub(crate) const DEFAULT_API_BASE_URL: &str = "https://api.agentcat.com";
pub(crate) trait Env: Send + Sync + fmt::Debug {
fn var(&self, name: &str) -> Option<String>;
fn home_dir(&self) -> Option<PathBuf>;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ProcessEnv;
impl Env for ProcessEnv {
#[expect(
clippy::disallowed_methods,
reason = "the one place that reads the process environment"
)]
fn var(&self, name: &str) -> Option<String> {
std::env::var(name).ok()
}
fn home_dir(&self) -> Option<PathBuf> {
std::env::home_dir()
}
}
#[cfg(test)]
#[derive(Debug, Clone, Default)]
pub(crate) struct MapEnv {
vars: std::collections::HashMap<String, String>,
home: Option<PathBuf>,
}
#[cfg(test)]
impl MapEnv {
pub(crate) fn new(vars: &[(&str, &str)]) -> Self {
Self {
vars: vars
.iter()
.map(|&(name, value)| (name.to_owned(), value.to_owned()))
.collect(),
home: None,
}
}
pub(crate) fn with_home(mut self, home: PathBuf) -> Self {
self.home = Some(home);
self
}
}
#[cfg(test)]
impl Env for MapEnv {
fn var(&self, name: &str) -> Option<String> {
self.vars.get(name).cloned()
}
fn home_dir(&self) -> Option<PathBuf> {
self.home.clone()
}
}
pub(crate) fn resolve_api_base_url(options: &Options, env: &dyn Env) -> String {
options
.api_base_url_option()
.map(str::to_owned)
.or_else(|| env.var(AGENTCAT_API_URL).filter(|url| !url.is_empty()))
.or_else(|| env.var(MCPCAT_API_URL).filter(|url| !url.is_empty()))
.unwrap_or_else(|| DEFAULT_API_BASE_URL.to_owned())
}
pub(crate) fn sdk_enabled(env: &dyn Env) -> bool {
let Some(value) = env.var(AGENTCAT_ENABLED) else {
return true;
};
let value = wire::trim(&value).to_lowercase();
!matches!(value.as_str(), "false" | "0" | "no" | "off")
}