use std::{path::PathBuf, str::FromStr, time::Duration};
use secrecy::{ExposeSecret, SecretString};
use crate::error::ServerError;
pub use cognee_core::pipeline_run_registry::RegistryConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Environment {
Dev,
#[default]
Prod,
Test,
}
impl FromStr for Environment {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"dev" | "development" => Ok(Environment::Dev),
"test" | "testing" => Ok(Environment::Test),
_ => Ok(Environment::Prod),
}
}
}
#[derive(Debug, Clone)]
pub struct HttpServerConfig {
pub host: String,
pub port: u16,
pub cors_allowed_origins: Vec<String>,
pub ui_app_url: String,
pub env: Environment,
pub require_authentication: bool,
pub jwt_secret: SecretString,
pub jwt_lifetime: Duration,
pub body_limit: usize,
pub pipeline_registry_max_runs: usize,
pub pipeline_registry_finished_retention_secs: u64,
pub pipeline_registry_channel_capacity: usize,
pub pipeline_registry_abort_writes_errored: bool,
pub notebook_run_timeout: Duration,
pub health_probe_llm: bool,
pub health_probe_timeout_ms: u64,
pub health_cache_ttl_ms: u64,
pub data_root_directory: PathBuf,
pub system_root_directory: PathBuf,
pub relational_db_url: String,
pub graph_provider: String,
pub graph_file_path: PathBuf,
pub vector_provider: String,
pub vector_db_url: String,
pub embedding_provider: String,
pub embedding_dimensions: u32,
pub embedding_model_name: String,
pub embedding_model_path: Option<PathBuf>,
pub embedding_tokenizer_path: Option<PathBuf>,
pub embedding_endpoint: String,
pub embedding_api_key: SecretString,
pub llm_provider: String,
pub llm_model: String,
pub llm_api_key: SecretString,
pub llm_endpoint: String,
pub llm_max_retries: u32,
pub session_store_backend: String,
pub session_root_directory: PathBuf,
pub notebook_runner_enabled: bool,
pub responses_client_enabled: bool,
pub disable_default_backends: bool,
pub default_user_email: String,
}
fn default_cache_root() -> PathBuf {
if let Ok(xdg) = std::env::var("XDG_CACHE_HOME") {
PathBuf::from(xdg).join("cognee")
} else if let Ok(home) = std::env::var("HOME") {
PathBuf::from(home).join(".cache").join("cognee")
} else {
PathBuf::from("./.cognee")
}
}
fn parse_env_bool_with_default(v: &str, default: bool) -> bool {
if cognee_utils::parse_env_bool(v) {
true
} else {
let trimmed = v.trim().to_ascii_lowercase();
if matches!(trimmed.as_str(), "false" | "0" | "no" | "off") {
false
} else {
default
}
}
}
fn first_non_empty_env(keys: &[&str]) -> Option<String> {
for key in keys {
if let Ok(v) = std::env::var(key) {
let trimmed = v.trim();
if !trimmed.is_empty() {
return Some(trimmed.to_string());
}
}
}
None
}
fn default_relational_db_url(system_root_directory: &std::path::Path) -> String {
format!(
"sqlite://{}",
system_root_directory.join("cognee.db").display()
)
}
fn default_graph_file_path(system_root_directory: &std::path::Path) -> PathBuf {
system_root_directory.join("graph")
}
fn default_vector_db_url(system_root_directory: &std::path::Path) -> String {
system_root_directory.join("vectors").display().to_string()
}
fn default_session_root_directory(system_root_directory: &std::path::Path) -> PathBuf {
system_root_directory.join("sessions")
}
impl Default for HttpServerConfig {
fn default() -> Self {
let cache_root = default_cache_root();
let data_root = cache_root.join("data");
let system_root = cache_root.join("system");
Self {
host: "0.0.0.0".into(),
port: 8000,
cors_allowed_origins: Vec::new(),
ui_app_url: "http://localhost:3000".into(),
env: Environment::Prod,
require_authentication: false,
jwt_secret: SecretString::new(uuid::Uuid::new_v4().to_string().into()),
jwt_lifetime: Duration::from_secs(3600),
body_limit: 100 * 1024 * 1024,
pipeline_registry_max_runs: 4096,
pipeline_registry_finished_retention_secs: 3600,
pipeline_registry_channel_capacity: 64,
pipeline_registry_abort_writes_errored: true,
notebook_run_timeout: Duration::from_secs(30),
health_probe_llm: false,
health_probe_timeout_ms: 2000,
health_cache_ttl_ms: 5000,
data_root_directory: data_root,
system_root_directory: system_root.clone(),
relational_db_url: default_relational_db_url(&system_root),
graph_provider: "ladybug".to_string(),
graph_file_path: default_graph_file_path(&system_root),
vector_provider: "pgvector".to_string(),
vector_db_url: default_vector_db_url(&system_root),
embedding_provider: "onnx".to_string(),
embedding_dimensions: 384,
embedding_model_name: "bge-small-en-v1.5".to_string(),
embedding_model_path: None,
embedding_tokenizer_path: None,
embedding_endpoint: String::new(),
embedding_api_key: SecretString::new(String::new().into()),
llm_provider: "openai".to_string(),
llm_model: "gpt-4o-mini".to_string(),
llm_api_key: SecretString::new(String::new().into()),
llm_endpoint: String::new(),
llm_max_retries: 3,
session_store_backend: "seaorm".to_string(),
session_root_directory: default_session_root_directory(&system_root),
notebook_runner_enabled: false,
responses_client_enabled: false,
disable_default_backends: false,
default_user_email: "default_user@example.com".to_string(),
}
}
}
impl HttpServerConfig {
pub fn from_env() -> Result<Self, ServerError> {
let mut cfg = Self::default();
let default_system_root_directory = cfg.system_root_directory.clone();
if let Ok(v) = std::env::var("HTTP_API_HOST") {
cfg.host = v;
}
if let Ok(v) = std::env::var("HTTP_API_PORT") {
cfg.port = v
.parse::<u16>()
.map_err(|e| ServerError::Other(anyhow::anyhow!("HTTP_API_PORT: {e}")))?;
}
if let Ok(v) = std::env::var("CORS_ALLOWED_ORIGINS") {
cfg.cors_allowed_origins = v
.split(',')
.map(|s| s.trim().to_owned())
.filter(|s| !s.is_empty())
.collect();
}
if let Ok(v) = std::env::var("UI_APP_URL") {
cfg.ui_app_url = v;
}
if let Ok(v) = std::env::var("ENV") {
cfg.env = v.parse().unwrap_or(Environment::Prod);
}
if let Ok(v) = std::env::var("REQUIRE_AUTHENTICATION") {
cfg.require_authentication =
!matches!(v.to_ascii_lowercase().as_str(), "false" | "0" | "no");
}
if let Ok(v) = std::env::var("AUTH_JWT_SECRET") {
cfg.jwt_secret = SecretString::new(v.into());
}
if let Ok(v) = std::env::var("AUTH_JWT_LIFETIME_SECONDS") {
let secs = v.parse::<u64>().map_err(|e| {
ServerError::Other(anyhow::anyhow!("AUTH_JWT_LIFETIME_SECONDS: {e}"))
})?;
cfg.jwt_lifetime = Duration::from_secs(secs);
}
if let Ok(v) = std::env::var("HTTP_BODY_LIMIT_BYTES") {
cfg.body_limit = v
.parse::<usize>()
.map_err(|e| ServerError::Other(anyhow::anyhow!("HTTP_BODY_LIMIT_BYTES: {e}")))?;
}
if let Ok(v) = std::env::var("PIPELINE_REGISTRY_MAX_RUNS") {
cfg.pipeline_registry_max_runs = v.parse::<usize>().map_err(|e| {
ServerError::Other(anyhow::anyhow!("PIPELINE_REGISTRY_MAX_RUNS: {e}"))
})?;
}
if let Ok(v) = std::env::var("PIPELINE_REGISTRY_FINISHED_RETENTION_SECS") {
cfg.pipeline_registry_finished_retention_secs = v.parse::<u64>().map_err(|e| {
ServerError::Other(anyhow::anyhow!(
"PIPELINE_REGISTRY_FINISHED_RETENTION_SECS: {e}"
))
})?;
}
if let Ok(v) = std::env::var("PIPELINE_REGISTRY_CHANNEL_CAPACITY") {
cfg.pipeline_registry_channel_capacity = v.parse::<usize>().map_err(|e| {
ServerError::Other(anyhow::anyhow!("PIPELINE_REGISTRY_CHANNEL_CAPACITY: {e}"))
})?;
}
if let Ok(v) = std::env::var("PIPELINE_REGISTRY_ABORT_WRITES_ERRORED") {
cfg.pipeline_registry_abort_writes_errored =
!matches!(v.to_ascii_lowercase().as_str(), "false" | "0" | "no");
}
if let Ok(v) = std::env::var("NOTEBOOK_RUN_TIMEOUT_SECS") {
let secs = v.parse::<u64>().map_err(|e| {
ServerError::Other(anyhow::anyhow!("NOTEBOOK_RUN_TIMEOUT_SECS: {e}"))
})?;
cfg.notebook_run_timeout = Duration::from_secs(secs);
}
if let Ok(v) = std::env::var("COGNEE_HEALTH_PROBE_LLM") {
cfg.health_probe_llm =
matches!(v.to_ascii_lowercase().as_str(), "true" | "1" | "yes" | "on");
}
if let Ok(v) = std::env::var("COGNEE_HEALTH_PROBE_TIMEOUT_MS") {
cfg.health_probe_timeout_ms = v.parse::<u64>().map_err(|e| {
ServerError::Other(anyhow::anyhow!("COGNEE_HEALTH_PROBE_TIMEOUT_MS: {e}"))
})?;
}
if let Ok(v) = std::env::var("COGNEE_HEALTH_CACHE_TTL_MS") {
cfg.health_cache_ttl_ms = v.parse::<u64>().map_err(|e| {
ServerError::Other(anyhow::anyhow!("COGNEE_HEALTH_CACHE_TTL_MS: {e}"))
})?;
}
if let Ok(v) = std::env::var("DATA_ROOT_DIRECTORY") {
cfg.data_root_directory = PathBuf::from(v);
}
if let Ok(v) = std::env::var("SYSTEM_ROOT_DIRECTORY") {
cfg.system_root_directory = PathBuf::from(v);
if cfg.relational_db_url == default_relational_db_url(&default_system_root_directory) {
cfg.relational_db_url = default_relational_db_url(&cfg.system_root_directory);
}
if cfg.graph_file_path == default_graph_file_path(&default_system_root_directory) {
cfg.graph_file_path = default_graph_file_path(&cfg.system_root_directory);
}
if cfg.vector_db_url == default_vector_db_url(&default_system_root_directory) {
cfg.vector_db_url = default_vector_db_url(&cfg.system_root_directory);
}
if cfg.session_root_directory
== default_session_root_directory(&default_system_root_directory)
{
cfg.session_root_directory =
default_session_root_directory(&cfg.system_root_directory);
}
}
if let Some(v) = first_non_empty_env(&["RELATIONAL_DB_URL", "DATABASE_URL"]) {
cfg.relational_db_url = v;
}
if let Ok(v) = std::env::var("GRAPH_DATABASE_PROVIDER") {
cfg.graph_provider = v;
}
if let Ok(v) = std::env::var("GRAPH_FILE_PATH") {
cfg.graph_file_path = PathBuf::from(v);
}
if let Ok(v) = std::env::var("VECTOR_DB_PROVIDER") {
cfg.vector_provider = v;
}
if let Ok(v) = std::env::var("VECTOR_DB_URL") {
cfg.vector_db_url = v;
}
if let Ok(v) = std::env::var("EMBEDDING_PROVIDER") {
cfg.embedding_provider = v;
}
if let Ok(v) = std::env::var("EMBEDDING_DIMENSIONS") {
cfg.embedding_dimensions = v
.parse::<u32>()
.map_err(|e| ServerError::Other(anyhow::anyhow!("EMBEDDING_DIMENSIONS: {e}")))?;
}
if let Some(v) = first_non_empty_env(&["EMBEDDING_MODEL_NAME", "EMBEDDING_MODEL"]) {
cfg.embedding_model_name = v;
}
if let Ok(v) = std::env::var("EMBEDDING_MODEL_PATH") {
cfg.embedding_model_path = Some(PathBuf::from(v));
}
if let Ok(v) = std::env::var("EMBEDDING_TOKENIZER_PATH") {
cfg.embedding_tokenizer_path = Some(PathBuf::from(v));
}
if let Ok(v) = std::env::var("EMBEDDING_ENDPOINT") {
cfg.embedding_endpoint = v;
}
if let Some(v) = first_non_empty_env(&["EMBEDDING_API_KEY", "LLM_API_KEY", "OPENAI_TOKEN"])
{
cfg.embedding_api_key = SecretString::new(v.into());
}
if let Ok(v) = std::env::var("LLM_PROVIDER") {
cfg.llm_provider = v;
}
if let Some(v) = first_non_empty_env(&["LLM_MODEL", "OPENAI_MODEL"]) {
cfg.llm_model = v;
}
if let Some(v) = first_non_empty_env(&["LLM_API_KEY", "OPENAI_TOKEN"]) {
cfg.llm_api_key = SecretString::new(v.into());
}
if let Some(v) = first_non_empty_env(&["LLM_ENDPOINT", "OPENAI_URL"]) {
cfg.llm_endpoint = v;
}
if let Ok(v) = std::env::var("LLM_MAX_RETRIES") {
cfg.llm_max_retries = v
.parse::<u32>()
.map_err(|e| ServerError::Other(anyhow::anyhow!("LLM_MAX_RETRIES: {e}")))?;
}
if let Ok(v) = std::env::var("COGNEE_SESSION_STORE") {
cfg.session_store_backend = v;
}
if let Ok(v) = std::env::var("COGNEE_SESSION_DIR") {
cfg.session_root_directory = PathBuf::from(v);
}
if let Ok(v) = std::env::var("COGNEE_NOTEBOOK_RUNNER_ENABLED") {
cfg.notebook_runner_enabled = cognee_utils::parse_env_bool(&v);
}
if let Ok(v) = std::env::var("COGNEE_RESPONSES_CLIENT_ENABLED") {
cfg.responses_client_enabled = parse_env_bool_with_default(&v, false);
} else {
cfg.responses_client_enabled = !cfg.llm_api_key.expose_secret().is_empty();
}
if let Ok(v) = std::env::var("COGNEE_DISABLE_DEFAULT_BACKENDS") {
cfg.disable_default_backends = cognee_utils::parse_env_bool(&v);
}
if let Ok(v) = std::env::var("DEFAULT_USER_EMAIL") {
let trimmed = v.trim();
if !trimmed.is_empty() {
cfg.default_user_email = trimmed.to_string();
}
}
Ok(cfg)
}
}
impl HttpServerConfig {
pub fn to_registry_config(&self) -> RegistryConfig {
RegistryConfig {
max_in_memory_runs: self.pipeline_registry_max_runs,
finished_retention: Duration::from_secs(self.pipeline_registry_finished_retention_secs),
channel_capacity: self.pipeline_registry_channel_capacity,
yield_throttle: None, abort_writes_errored_row: self.pipeline_registry_abort_writes_errored,
}
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test code — panics are acceptable failures"
)]
mod tests {
use super::*;
use secrecy::ExposeSecret;
#[test]
fn test_defaults() {
let cfg = HttpServerConfig::default();
assert_eq!(cfg.host, "0.0.0.0");
assert_eq!(cfg.port, 8000);
assert_eq!(cfg.ui_app_url, "http://localhost:3000");
assert_eq!(cfg.body_limit, 100 * 1024 * 1024);
assert_eq!(cfg.jwt_lifetime, Duration::from_secs(3600));
assert!(!cfg.require_authentication);
assert!(cfg.cors_allowed_origins.is_empty());
assert_eq!(cfg.env, Environment::Prod);
}
#[test]
fn test_env_override_port() {
unsafe {
std::env::set_var("HTTP_API_PORT", "9999");
}
let cfg = HttpServerConfig::from_env().expect("from_env");
unsafe {
std::env::remove_var("HTTP_API_PORT");
}
assert_eq!(cfg.port, 9999);
}
#[test]
fn test_env_cors_origins() {
unsafe {
std::env::set_var("CORS_ALLOWED_ORIGINS", "http://a.test, http://b.test");
}
let cfg = HttpServerConfig::from_env().expect("from_env");
unsafe {
std::env::remove_var("CORS_ALLOWED_ORIGINS");
}
assert_eq!(
cfg.cors_allowed_origins,
vec!["http://a.test", "http://b.test"]
);
}
#[test]
fn test_environment_from_str() {
assert_eq!("dev".parse::<Environment>().unwrap(), Environment::Dev);
assert_eq!("test".parse::<Environment>().unwrap(), Environment::Test);
assert_eq!("prod".parse::<Environment>().unwrap(), Environment::Prod);
assert_eq!(
"anything".parse::<Environment>().unwrap(),
Environment::Prod
);
}
#[test]
fn test_bool_backend_flags_from_env() {
unsafe {
std::env::set_var("COGNEE_NOTEBOOK_RUNNER_ENABLED", "yes");
std::env::set_var("COGNEE_RESPONSES_CLIENT_ENABLED", "1");
std::env::set_var("COGNEE_DISABLE_DEFAULT_BACKENDS", "true");
}
let cfg = HttpServerConfig::from_env().expect("from_env");
unsafe {
std::env::remove_var("COGNEE_NOTEBOOK_RUNNER_ENABLED");
std::env::remove_var("COGNEE_RESPONSES_CLIENT_ENABLED");
std::env::remove_var("COGNEE_DISABLE_DEFAULT_BACKENDS");
}
assert!(cfg.notebook_runner_enabled);
assert!(cfg.responses_client_enabled);
assert!(cfg.disable_default_backends);
}
#[test]
fn test_llm_fallback_env_aliases() {
unsafe {
std::env::set_var("OPENAI_TOKEN", "test-key");
std::env::set_var("OPENAI_MODEL", "gpt-test");
std::env::set_var("OPENAI_URL", "https://example.test/v1");
std::env::remove_var("LLM_API_KEY");
std::env::remove_var("LLM_MODEL");
std::env::remove_var("LLM_ENDPOINT");
}
let cfg = HttpServerConfig::from_env().expect("from_env");
unsafe {
std::env::remove_var("OPENAI_TOKEN");
std::env::remove_var("OPENAI_MODEL");
std::env::remove_var("OPENAI_URL");
}
assert_eq!(cfg.llm_api_key.expose_secret(), "test-key");
assert_eq!(cfg.llm_model, "gpt-test");
assert_eq!(cfg.llm_endpoint, "https://example.test/v1");
}
#[test]
fn test_system_root_directory_rebases_dependent_defaults() {
let temp = tempfile::tempdir().expect("tempdir");
let new_root = temp.path().join("custom-system-root");
unsafe {
std::env::set_var("SYSTEM_ROOT_DIRECTORY", &new_root);
std::env::remove_var("RELATIONAL_DB_URL");
std::env::remove_var("DATABASE_URL");
std::env::remove_var("GRAPH_FILE_PATH");
std::env::remove_var("VECTOR_DB_URL");
std::env::remove_var("COGNEE_SESSION_DIR");
}
let cfg = HttpServerConfig::from_env().expect("from_env");
unsafe {
std::env::remove_var("SYSTEM_ROOT_DIRECTORY");
}
assert_eq!(cfg.system_root_directory, new_root);
assert_eq!(cfg.relational_db_url, default_relational_db_url(&new_root));
assert_eq!(cfg.graph_file_path, default_graph_file_path(&new_root));
assert_eq!(cfg.vector_db_url, default_vector_db_url(&new_root));
assert_eq!(
cfg.session_root_directory,
default_session_root_directory(&new_root)
);
}
}