use std::ffi::OsString;
use std::sync::OnceLock;
pub const OPENAI_API_KEY: &str = "OPENAI_API_KEY";
pub const ANTHROPIC_API_KEY: &str = "ANTHROPIC_API_KEY";
pub const DIFFLORE_FIX_DEBUG: &str = "DIFFLORE_FIX_DEBUG";
pub const DIFFLORE_FIX_DUMP_DIR: &str = "DIFFLORE_FIX_DUMP_DIR";
pub const DIFFLORE_FIX_PREVIEW_REVIEW_TIMEOUT_SECS: &str =
"DIFFLORE_FIX_PREVIEW_REVIEW_TIMEOUT_SECS";
pub const DIFFLORE_TRACE_HOOK: &str = "DIFFLORE_TRACE_HOOK";
pub const DIFFLORE_HOOK_CACHE_TTL_MS: &str = "DIFFLORE_HOOK_CACHE_TTL_MS";
pub const DIFFLORE_HOOK_SHORT_CIRCUIT: &str = "DIFFLORE_HOOK_SHORT_CIRCUIT";
pub const DIFFLORE_HOOK_CLIENT: &str = "DIFFLORE_HOOK_CLIENT";
pub const DIFFLORE_HOOK_FORWARD: &str = "DIFFLORE_HOOK_FORWARD";
pub const DIFFLORE_DEBUG_HOOKS: &str = "DIFFLORE_DEBUG_HOOKS";
pub const DIFFLORE_HOOK_SHIM_TRACE: &str = "DIFFLORE_HOOK_SHIM_TRACE";
pub const DIFFLORE_HOOK_CROSS_REPO_STARTER: &str = "DIFFLORE_HOOK_CROSS_REPO_STARTER";
pub const DIFFLORE_MASTER_KEY: &str = "DIFFLORE_MASTER_KEY";
pub const DIFFLORE_HOME: &str = "DIFFLORE_HOME";
pub const DIFFLORE_MCP_HOME: &str = "DIFFLORE_MCP_HOME";
pub const DIFFLORE_NO_WELCOME: &str = "DIFFLORE_NO_WELCOME";
pub const DIFFLORE_CLOUD_TOKEN: &str = "DIFFLORE_CLOUD_TOKEN";
pub const DIFFLORE_EMBEDDING_KEY: &str = "DIFFLORE_EMBEDDING_KEY";
pub const DIFFLORE_TOKEN: &str = "DIFFLORE_TOKEN";
pub const DIFFLORE_DEBUG_CLOUD: &str = "DIFFLORE_DEBUG_CLOUD";
pub const DIFFLORE_DEBUG_TELEMETRY: &str = "DIFFLORE_DEBUG_TELEMETRY";
pub const DIFFLORE_DEBUG_PROVIDERS: &str = "DIFFLORE_DEBUG_PROVIDERS";
pub const DIFFLORE_BFS_RETRIEVAL: &str = "DIFFLORE_BFS_RETRIEVAL";
pub const DIFFLORE_INTENT_RERANK: &str = "DIFFLORE_INTENT_RERANK";
pub const DIFFLORE_DISABLE_RULES: &str = "DIFFLORE_DISABLE_RULES";
pub const DIFFLORE_DEEP_RECALL_SAMPLE_RATE: &str = "DIFFLORE_DEEP_RECALL_SAMPLE_RATE";
pub const DIFFLORE_CLAUDE_HOME: &str = "DIFFLORE_CLAUDE_HOME";
pub const DIFFLORE_CLOUD_URL: &str = "DIFFLORE_CLOUD_URL";
pub const DIFF_LORE_CLOUD_URL: &str = "DIFF_LORE_CLOUD_URL";
pub const NO_COLOR: &str = "NO_COLOR";
pub const COLORTERM: &str = "COLORTERM";
pub const TERM: &str = "TERM";
pub const PATH: &str = "PATH";
pub const COLORFGBG: &str = "COLORFGBG";
pub const DIFFLORE_THEME: &str = "DIFFLORE_THEME";
#[must_use]
pub fn var(name: &str) -> Option<String> {
std::env::var(name).ok()
}
#[must_use]
pub fn var_os(name: &str) -> Option<OsString> {
std::env::var_os(name)
}
#[must_use]
pub fn flag_set(name: &str) -> bool {
std::env::var_os(name).is_some()
}
#[must_use]
pub fn non_empty(name: &str) -> Option<String> {
std::env::var(name).ok().filter(|v| !v.is_empty())
}
#[must_use]
pub fn truthy(name: &str) -> bool {
matches!(std::env::var(name), Ok(v) if !v.is_empty() && v != "0" && v != "false")
}
#[must_use]
pub fn fix_debug() -> bool {
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| flag_set(DIFFLORE_FIX_DEBUG))
}
#[must_use]
pub fn trace_hook() -> bool {
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| flag_set(DIFFLORE_TRACE_HOOK))
}
#[must_use]
pub fn hook_cross_repo_starter_enabled() -> bool {
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| truthy(DIFFLORE_HOOK_CROSS_REPO_STARTER))
}
#[must_use]
pub fn debug_cloud() -> bool {
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| flag_set(DIFFLORE_DEBUG_CLOUD))
}
#[must_use]
pub fn debug_telemetry() -> bool {
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| flag_set(DIFFLORE_DEBUG_TELEMETRY))
}
#[must_use]
pub fn debug_providers() -> bool {
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| flag_set(DIFFLORE_DEBUG_PROVIDERS))
}
#[must_use]
pub fn hook_cache_ttl_ms() -> Option<u64> {
var(DIFFLORE_HOOK_CACHE_TTL_MS).and_then(|v| v.parse().ok())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HookShortCircuitMode {
Auto,
Off,
Force,
}
impl HookShortCircuitMode {
#[must_use]
pub fn parse(raw: &str) -> Self {
match raw.trim().to_ascii_lowercase().as_str() {
"off" | "disable" | "disabled" | "0" | "false" => Self::Off,
"force" | "always" => Self::Force,
_ => Self::Auto,
}
}
}
#[must_use]
pub fn hook_short_circuit_mode() -> HookShortCircuitMode {
match var(DIFFLORE_HOOK_SHORT_CIRCUIT) {
Some(v) if !v.is_empty() => HookShortCircuitMode::parse(&v),
_ => HookShortCircuitMode::Auto,
}
}
pub const DEFAULT_DEEP_RECALL_SAMPLE_RATE: f32 = 0.02;
pub const MAX_DEEP_RECALL_SAMPLE_RATE: f32 = 0.10;
pub fn parse_deep_recall_sample_rate(raw: &str) -> Result<f32, String> {
let trimmed = raw.trim();
if trimmed.is_empty() {
return Err(format!(
"{DIFFLORE_DEEP_RECALL_SAMPLE_RATE} is empty; unset it to use the default \
({DEFAULT_DEEP_RECALL_SAMPLE_RATE}) or pass a value in \
[0.0, {MAX_DEEP_RECALL_SAMPLE_RATE}]"
));
}
let parsed: f32 = trimmed.parse().map_err(|_| {
format!(
"{DIFFLORE_DEEP_RECALL_SAMPLE_RATE}={raw:?} is not a valid f32; expected a \
number in [0.0, {MAX_DEEP_RECALL_SAMPLE_RATE}]"
)
})?;
if !parsed.is_finite() {
return Err(format!(
"{DIFFLORE_DEEP_RECALL_SAMPLE_RATE}={raw:?} must be finite; got {parsed}"
));
}
if !(0.0..=MAX_DEEP_RECALL_SAMPLE_RATE).contains(&parsed) {
return Err(format!(
"{DIFFLORE_DEEP_RECALL_SAMPLE_RATE}={parsed} is out of range; expected \
[0.0, {MAX_DEEP_RECALL_SAMPLE_RATE}]"
));
}
Ok(parsed)
}
#[must_use]
pub fn deep_recall_sample_rate() -> f32 {
match var(DIFFLORE_DEEP_RECALL_SAMPLE_RATE) {
Some(raw) => match parse_deep_recall_sample_rate(&raw) {
Ok(rate) => rate,
Err(msg) => {
eprintln!(
"[difflore] invalid {DIFFLORE_DEEP_RECALL_SAMPLE_RATE}: {msg}; \
falling back to default {DEFAULT_DEEP_RECALL_SAMPLE_RATE}"
);
DEFAULT_DEEP_RECALL_SAMPLE_RATE
}
},
None => DEFAULT_DEEP_RECALL_SAMPLE_RATE,
}
}
#[must_use]
pub fn master_key_hex() -> Option<String> {
var(DIFFLORE_MASTER_KEY)
}
#[must_use]
pub fn difflore_home() -> Option<OsString> {
var_os(DIFFLORE_HOME)
}
#[must_use]
pub fn fix_dump_dir() -> Option<String> {
var(DIFFLORE_FIX_DUMP_DIR)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_deep_recall_sample_rate_accepts_in_range_values() {
for raw in ["0.0", "0.02", "0.05", "0.10", " 0.02 "] {
let parsed = parse_deep_recall_sample_rate(raw)
.unwrap_or_else(|e| panic!("expected {raw:?} to parse, got error: {e}"));
assert!(
(0.0..=MAX_DEEP_RECALL_SAMPLE_RATE).contains(&parsed),
"{raw:?} parsed to {parsed}, outside the permitted range"
);
}
assert!((parse_deep_recall_sample_rate("0.02").unwrap() - 0.02).abs() < 1e-6);
assert!((parse_deep_recall_sample_rate("0.0").unwrap()).abs() < 1e-6);
}
#[test]
fn hook_short_circuit_mode_parses_each_state() {
assert_eq!(
HookShortCircuitMode::parse("auto"),
HookShortCircuitMode::Auto
);
assert_eq!(HookShortCircuitMode::parse(""), HookShortCircuitMode::Auto);
assert_eq!(
HookShortCircuitMode::parse("AUTO"),
HookShortCircuitMode::Auto
);
assert_eq!(
HookShortCircuitMode::parse("off"),
HookShortCircuitMode::Off
);
assert_eq!(
HookShortCircuitMode::parse("OFF"),
HookShortCircuitMode::Off
);
assert_eq!(
HookShortCircuitMode::parse("disabled"),
HookShortCircuitMode::Off
);
assert_eq!(HookShortCircuitMode::parse("0"), HookShortCircuitMode::Off);
assert_eq!(
HookShortCircuitMode::parse("false"),
HookShortCircuitMode::Off
);
assert_eq!(
HookShortCircuitMode::parse("force"),
HookShortCircuitMode::Force
);
assert_eq!(
HookShortCircuitMode::parse("FORCE"),
HookShortCircuitMode::Force
);
assert_eq!(
HookShortCircuitMode::parse("always"),
HookShortCircuitMode::Force
);
assert_eq!(
HookShortCircuitMode::parse("yolo"),
HookShortCircuitMode::Auto
);
}
#[test]
fn parse_deep_recall_sample_rate_rejects_out_of_range_and_garbage() {
assert!(parse_deep_recall_sample_rate("0.5").is_err());
assert!(parse_deep_recall_sample_rate("1.0").is_err());
assert!(parse_deep_recall_sample_rate("-0.01").is_err());
assert!(parse_deep_recall_sample_rate("two").is_err());
assert!(parse_deep_recall_sample_rate("").is_err());
assert!(parse_deep_recall_sample_rate(" ").is_err());
assert!(parse_deep_recall_sample_rate("NaN").is_err());
assert!(parse_deep_recall_sample_rate("inf").is_err());
}
}