1use std::{path::PathBuf, str::FromStr, time::Duration};
9
10use secrecy::{ExposeSecret, SecretString};
11
12use crate::error::ServerError;
13
14pub use cognee_core::pipeline_run_registry::RegistryConfig;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum Environment {
23 Dev,
24 #[default]
25 Prod,
26 Test,
27}
28
29impl FromStr for Environment {
30 type Err = ();
31 fn from_str(s: &str) -> Result<Self, Self::Err> {
32 match s.to_ascii_lowercase().as_str() {
33 "dev" | "development" => Ok(Environment::Dev),
34 "test" | "testing" => Ok(Environment::Test),
35 _ => Ok(Environment::Prod),
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
46pub struct HttpServerConfig {
47 pub host: String,
49 pub port: u16,
51 pub cors_allowed_origins: Vec<String>,
54 pub ui_app_url: String,
57 pub env: Environment,
59 pub require_authentication: bool,
70 pub jwt_secret: SecretString,
73 pub jwt_lifetime: Duration,
75 pub body_limit: usize,
78
79 pub pipeline_registry_max_runs: usize,
85 pub pipeline_registry_finished_retention_secs: u64,
88 pub pipeline_registry_channel_capacity: usize,
91 pub pipeline_registry_abort_writes_errored: bool,
96
97 pub notebook_run_timeout: Duration,
100
101 pub health_probe_llm: bool,
110
111 pub health_probe_timeout_ms: u64,
116
117 pub health_cache_ttl_ms: u64,
123
124 pub data_root_directory: PathBuf,
128
129 pub system_root_directory: PathBuf,
132
133 pub relational_db_url: String,
136
137 pub graph_provider: String,
140
141 pub graph_file_path: PathBuf,
144
145 pub vector_provider: String,
152
153 pub vector_db_url: String,
156
157 pub embedding_provider: String,
160
161 pub embedding_dimensions: u32,
164
165 pub embedding_model_name: String,
168
169 pub embedding_model_path: Option<PathBuf>,
172
173 pub embedding_tokenizer_path: Option<PathBuf>,
176
177 pub embedding_endpoint: String,
180
181 pub embedding_api_key: SecretString,
184
185 pub llm_provider: String,
188
189 pub llm_model: String,
192
193 pub llm_api_key: SecretString,
196
197 pub llm_endpoint: String,
200
201 pub llm_max_retries: u32,
204
205 pub session_store_backend: String,
208
209 pub session_root_directory: PathBuf,
212
213 pub notebook_runner_enabled: bool,
216
217 pub responses_client_enabled: bool,
220
221 pub disable_default_backends: bool,
224
225 pub default_user_email: String,
239}
240
241fn default_cache_root() -> PathBuf {
242 if let Ok(xdg) = std::env::var("XDG_CACHE_HOME") {
243 PathBuf::from(xdg).join("cognee")
244 } else if let Ok(home) = std::env::var("HOME") {
245 PathBuf::from(home).join(".cache").join("cognee")
246 } else {
247 PathBuf::from("./.cognee")
248 }
249}
250
251fn parse_env_bool_with_default(v: &str, default: bool) -> bool {
252 if cognee_utils::parse_env_bool(v) {
253 true
254 } else {
255 let trimmed = v.trim().to_ascii_lowercase();
256 if matches!(trimmed.as_str(), "false" | "0" | "no" | "off") {
257 false
258 } else {
259 default
260 }
261 }
262}
263
264fn first_non_empty_env(keys: &[&str]) -> Option<String> {
265 for key in keys {
266 if let Ok(v) = std::env::var(key) {
267 let trimmed = v.trim();
268 if !trimmed.is_empty() {
269 return Some(trimmed.to_string());
270 }
271 }
272 }
273 None
274}
275
276fn default_relational_db_url(system_root_directory: &std::path::Path) -> String {
277 format!(
278 "sqlite://{}",
279 system_root_directory.join("cognee.db").display()
280 )
281}
282
283fn default_graph_file_path(system_root_directory: &std::path::Path) -> PathBuf {
284 system_root_directory.join("graph")
285}
286
287fn default_vector_db_url(system_root_directory: &std::path::Path) -> String {
288 system_root_directory.join("vectors").display().to_string()
289}
290
291fn default_session_root_directory(system_root_directory: &std::path::Path) -> PathBuf {
292 system_root_directory.join("sessions")
293}
294
295impl Default for HttpServerConfig {
296 fn default() -> Self {
297 let cache_root = default_cache_root();
298 let data_root = cache_root.join("data");
299 let system_root = cache_root.join("system");
300 Self {
301 host: "0.0.0.0".into(),
302 port: 8000,
303 cors_allowed_origins: Vec::new(),
304 ui_app_url: "http://localhost:3000".into(),
305 env: Environment::Prod,
306 require_authentication: false,
307 jwt_secret: SecretString::new(uuid::Uuid::new_v4().to_string().into()),
308 jwt_lifetime: Duration::from_secs(3600),
309 body_limit: 100 * 1024 * 1024,
310 pipeline_registry_max_runs: 4096,
311 pipeline_registry_finished_retention_secs: 3600,
312 pipeline_registry_channel_capacity: 64,
313 pipeline_registry_abort_writes_errored: true,
314 notebook_run_timeout: Duration::from_secs(30),
315 health_probe_llm: false,
316 health_probe_timeout_ms: 2000,
317 health_cache_ttl_ms: 5000,
318 data_root_directory: data_root,
319 system_root_directory: system_root.clone(),
320 relational_db_url: default_relational_db_url(&system_root),
321 graph_provider: "ladybug".to_string(),
322 graph_file_path: default_graph_file_path(&system_root),
323 vector_provider: "pgvector".to_string(),
324 vector_db_url: default_vector_db_url(&system_root),
325 embedding_provider: "onnx".to_string(),
326 embedding_dimensions: 384,
327 embedding_model_name: "bge-small-en-v1.5".to_string(),
328 embedding_model_path: None,
329 embedding_tokenizer_path: None,
330 embedding_endpoint: String::new(),
331 embedding_api_key: SecretString::new(String::new().into()),
332 llm_provider: "openai".to_string(),
333 llm_model: "gpt-4o-mini".to_string(),
334 llm_api_key: SecretString::new(String::new().into()),
335 llm_endpoint: String::new(),
336 llm_max_retries: 3,
337 session_store_backend: "seaorm".to_string(),
338 session_root_directory: default_session_root_directory(&system_root),
339 notebook_runner_enabled: false,
340 responses_client_enabled: false,
341 disable_default_backends: false,
342 default_user_email: "default_user@example.com".to_string(),
343 }
344 }
345}
346
347impl HttpServerConfig {
348 pub fn from_env() -> Result<Self, ServerError> {
353 let mut cfg = Self::default();
354 let default_system_root_directory = cfg.system_root_directory.clone();
355
356 if let Ok(v) = std::env::var("HTTP_API_HOST") {
357 cfg.host = v;
358 }
359 if let Ok(v) = std::env::var("HTTP_API_PORT") {
360 cfg.port = v
361 .parse::<u16>()
362 .map_err(|e| ServerError::Other(anyhow::anyhow!("HTTP_API_PORT: {e}")))?;
363 }
364 if let Ok(v) = std::env::var("CORS_ALLOWED_ORIGINS") {
365 cfg.cors_allowed_origins = v
366 .split(',')
367 .map(|s| s.trim().to_owned())
368 .filter(|s| !s.is_empty())
369 .collect();
370 }
371 if let Ok(v) = std::env::var("UI_APP_URL") {
372 cfg.ui_app_url = v;
373 }
374 if let Ok(v) = std::env::var("ENV") {
375 cfg.env = v.parse().unwrap_or(Environment::Prod);
376 }
377 if let Ok(v) = std::env::var("REQUIRE_AUTHENTICATION") {
378 cfg.require_authentication =
379 !matches!(v.to_ascii_lowercase().as_str(), "false" | "0" | "no");
380 }
381 if let Ok(v) = std::env::var("AUTH_JWT_SECRET") {
382 cfg.jwt_secret = SecretString::new(v.into());
383 }
384 if let Ok(v) = std::env::var("AUTH_JWT_LIFETIME_SECONDS") {
385 let secs = v.parse::<u64>().map_err(|e| {
386 ServerError::Other(anyhow::anyhow!("AUTH_JWT_LIFETIME_SECONDS: {e}"))
387 })?;
388 cfg.jwt_lifetime = Duration::from_secs(secs);
389 }
390 if let Ok(v) = std::env::var("HTTP_BODY_LIMIT_BYTES") {
391 cfg.body_limit = v
392 .parse::<usize>()
393 .map_err(|e| ServerError::Other(anyhow::anyhow!("HTTP_BODY_LIMIT_BYTES: {e}")))?;
394 }
395
396 if let Ok(v) = std::env::var("PIPELINE_REGISTRY_MAX_RUNS") {
398 cfg.pipeline_registry_max_runs = v.parse::<usize>().map_err(|e| {
399 ServerError::Other(anyhow::anyhow!("PIPELINE_REGISTRY_MAX_RUNS: {e}"))
400 })?;
401 }
402 if let Ok(v) = std::env::var("PIPELINE_REGISTRY_FINISHED_RETENTION_SECS") {
403 cfg.pipeline_registry_finished_retention_secs = v.parse::<u64>().map_err(|e| {
404 ServerError::Other(anyhow::anyhow!(
405 "PIPELINE_REGISTRY_FINISHED_RETENTION_SECS: {e}"
406 ))
407 })?;
408 }
409 if let Ok(v) = std::env::var("PIPELINE_REGISTRY_CHANNEL_CAPACITY") {
410 cfg.pipeline_registry_channel_capacity = v.parse::<usize>().map_err(|e| {
411 ServerError::Other(anyhow::anyhow!("PIPELINE_REGISTRY_CHANNEL_CAPACITY: {e}"))
412 })?;
413 }
414 if let Ok(v) = std::env::var("PIPELINE_REGISTRY_ABORT_WRITES_ERRORED") {
415 cfg.pipeline_registry_abort_writes_errored =
416 !matches!(v.to_ascii_lowercase().as_str(), "false" | "0" | "no");
417 }
418
419 if let Ok(v) = std::env::var("NOTEBOOK_RUN_TIMEOUT_SECS") {
420 let secs = v.parse::<u64>().map_err(|e| {
421 ServerError::Other(anyhow::anyhow!("NOTEBOOK_RUN_TIMEOUT_SECS: {e}"))
422 })?;
423 cfg.notebook_run_timeout = Duration::from_secs(secs);
424 }
425
426 if let Ok(v) = std::env::var("COGNEE_HEALTH_PROBE_LLM") {
428 cfg.health_probe_llm =
429 matches!(v.to_ascii_lowercase().as_str(), "true" | "1" | "yes" | "on");
430 }
431 if let Ok(v) = std::env::var("COGNEE_HEALTH_PROBE_TIMEOUT_MS") {
432 cfg.health_probe_timeout_ms = v.parse::<u64>().map_err(|e| {
433 ServerError::Other(anyhow::anyhow!("COGNEE_HEALTH_PROBE_TIMEOUT_MS: {e}"))
434 })?;
435 }
436 if let Ok(v) = std::env::var("COGNEE_HEALTH_CACHE_TTL_MS") {
437 cfg.health_cache_ttl_ms = v.parse::<u64>().map_err(|e| {
438 ServerError::Other(anyhow::anyhow!("COGNEE_HEALTH_CACHE_TTL_MS: {e}"))
439 })?;
440 }
441
442 if let Ok(v) = std::env::var("DATA_ROOT_DIRECTORY") {
444 cfg.data_root_directory = PathBuf::from(v);
445 }
446 if let Ok(v) = std::env::var("SYSTEM_ROOT_DIRECTORY") {
447 cfg.system_root_directory = PathBuf::from(v);
448
449 if cfg.relational_db_url == default_relational_db_url(&default_system_root_directory) {
452 cfg.relational_db_url = default_relational_db_url(&cfg.system_root_directory);
453 }
454 if cfg.graph_file_path == default_graph_file_path(&default_system_root_directory) {
455 cfg.graph_file_path = default_graph_file_path(&cfg.system_root_directory);
456 }
457 if cfg.vector_db_url == default_vector_db_url(&default_system_root_directory) {
458 cfg.vector_db_url = default_vector_db_url(&cfg.system_root_directory);
459 }
460 if cfg.session_root_directory
461 == default_session_root_directory(&default_system_root_directory)
462 {
463 cfg.session_root_directory =
464 default_session_root_directory(&cfg.system_root_directory);
465 }
466 }
467
468 if let Some(v) = first_non_empty_env(&["RELATIONAL_DB_URL", "DATABASE_URL"]) {
469 cfg.relational_db_url = v;
470 }
471
472 if let Ok(v) = std::env::var("GRAPH_DATABASE_PROVIDER") {
473 cfg.graph_provider = v;
474 }
475 if let Ok(v) = std::env::var("GRAPH_FILE_PATH") {
476 cfg.graph_file_path = PathBuf::from(v);
477 }
478
479 if let Ok(v) = std::env::var("VECTOR_DB_PROVIDER") {
480 cfg.vector_provider = v;
481 }
482 if let Ok(v) = std::env::var("VECTOR_DB_URL") {
483 cfg.vector_db_url = v;
484 }
485
486 if let Ok(v) = std::env::var("EMBEDDING_PROVIDER") {
487 cfg.embedding_provider = v;
488 }
489 if let Ok(v) = std::env::var("EMBEDDING_DIMENSIONS") {
490 cfg.embedding_dimensions = v
491 .parse::<u32>()
492 .map_err(|e| ServerError::Other(anyhow::anyhow!("EMBEDDING_DIMENSIONS: {e}")))?;
493 }
494 if let Some(v) = first_non_empty_env(&["EMBEDDING_MODEL_NAME", "EMBEDDING_MODEL"]) {
495 cfg.embedding_model_name = v;
496 }
497 if let Ok(v) = std::env::var("EMBEDDING_MODEL_PATH") {
498 cfg.embedding_model_path = Some(PathBuf::from(v));
499 }
500 if let Ok(v) = std::env::var("EMBEDDING_TOKENIZER_PATH") {
501 cfg.embedding_tokenizer_path = Some(PathBuf::from(v));
502 }
503 if let Ok(v) = std::env::var("EMBEDDING_ENDPOINT") {
504 cfg.embedding_endpoint = v;
505 }
506 if let Some(v) = first_non_empty_env(&["EMBEDDING_API_KEY", "LLM_API_KEY", "OPENAI_TOKEN"])
507 {
508 cfg.embedding_api_key = SecretString::new(v.into());
509 }
510
511 if let Ok(v) = std::env::var("LLM_PROVIDER") {
512 cfg.llm_provider = v;
513 }
514 if let Some(v) = first_non_empty_env(&["LLM_MODEL", "OPENAI_MODEL"]) {
515 cfg.llm_model = v;
516 }
517 if let Some(v) = first_non_empty_env(&["LLM_API_KEY", "OPENAI_TOKEN"]) {
518 cfg.llm_api_key = SecretString::new(v.into());
519 }
520 if let Some(v) = first_non_empty_env(&["LLM_ENDPOINT", "OPENAI_URL"]) {
521 cfg.llm_endpoint = v;
522 }
523 if let Ok(v) = std::env::var("LLM_MAX_RETRIES") {
524 cfg.llm_max_retries = v
525 .parse::<u32>()
526 .map_err(|e| ServerError::Other(anyhow::anyhow!("LLM_MAX_RETRIES: {e}")))?;
527 }
528
529 if let Ok(v) = std::env::var("COGNEE_SESSION_STORE") {
530 cfg.session_store_backend = v;
531 }
532 if let Ok(v) = std::env::var("COGNEE_SESSION_DIR") {
533 cfg.session_root_directory = PathBuf::from(v);
534 }
535
536 if let Ok(v) = std::env::var("COGNEE_NOTEBOOK_RUNNER_ENABLED") {
537 cfg.notebook_runner_enabled = cognee_utils::parse_env_bool(&v);
538 }
539
540 if let Ok(v) = std::env::var("COGNEE_RESPONSES_CLIENT_ENABLED") {
541 cfg.responses_client_enabled = parse_env_bool_with_default(&v, false);
542 } else {
543 cfg.responses_client_enabled = !cfg.llm_api_key.expose_secret().is_empty();
544 }
545
546 if let Ok(v) = std::env::var("COGNEE_DISABLE_DEFAULT_BACKENDS") {
547 cfg.disable_default_backends = cognee_utils::parse_env_bool(&v);
548 }
549
550 if let Ok(v) = std::env::var("DEFAULT_USER_EMAIL") {
551 let trimmed = v.trim();
552 if !trimmed.is_empty() {
553 cfg.default_user_email = trimmed.to_string();
554 }
555 }
556
557 Ok(cfg)
558 }
559}
560
561impl HttpServerConfig {
562 pub fn to_registry_config(&self) -> RegistryConfig {
564 RegistryConfig {
565 max_in_memory_runs: self.pipeline_registry_max_runs,
566 finished_retention: Duration::from_secs(self.pipeline_registry_finished_retention_secs),
567 channel_capacity: self.pipeline_registry_channel_capacity,
568 yield_throttle: None, abort_writes_errored_row: self.pipeline_registry_abort_writes_errored,
570 }
571 }
572}
573
574#[cfg(test)]
577#[allow(
578 clippy::unwrap_used,
579 clippy::expect_used,
580 reason = "test code — panics are acceptable failures"
581)]
582mod tests {
583 use super::*;
584 use secrecy::ExposeSecret;
585
586 #[test]
587 fn test_defaults() {
588 let cfg = HttpServerConfig::default();
589 assert_eq!(cfg.host, "0.0.0.0");
590 assert_eq!(cfg.port, 8000);
591 assert_eq!(cfg.ui_app_url, "http://localhost:3000");
592 assert_eq!(cfg.body_limit, 100 * 1024 * 1024);
593 assert_eq!(cfg.jwt_lifetime, Duration::from_secs(3600));
594 assert!(!cfg.require_authentication);
598 assert!(cfg.cors_allowed_origins.is_empty());
599 assert_eq!(cfg.env, Environment::Prod);
600 }
601
602 #[test]
603 fn test_env_override_port() {
604 unsafe {
606 std::env::set_var("HTTP_API_PORT", "9999");
607 }
608 let cfg = HttpServerConfig::from_env().expect("from_env");
609 unsafe {
611 std::env::remove_var("HTTP_API_PORT");
612 }
613 assert_eq!(cfg.port, 9999);
614 }
615
616 #[test]
617 fn test_env_cors_origins() {
618 unsafe {
620 std::env::set_var("CORS_ALLOWED_ORIGINS", "http://a.test, http://b.test");
621 }
622 let cfg = HttpServerConfig::from_env().expect("from_env");
623 unsafe {
625 std::env::remove_var("CORS_ALLOWED_ORIGINS");
626 }
627 assert_eq!(
628 cfg.cors_allowed_origins,
629 vec!["http://a.test", "http://b.test"]
630 );
631 }
632
633 #[test]
634 fn test_environment_from_str() {
635 assert_eq!("dev".parse::<Environment>().unwrap(), Environment::Dev);
636 assert_eq!("test".parse::<Environment>().unwrap(), Environment::Test);
637 assert_eq!("prod".parse::<Environment>().unwrap(), Environment::Prod);
638 assert_eq!(
639 "anything".parse::<Environment>().unwrap(),
640 Environment::Prod
641 );
642 }
643
644 #[test]
645 fn test_bool_backend_flags_from_env() {
646 unsafe {
648 std::env::set_var("COGNEE_NOTEBOOK_RUNNER_ENABLED", "yes");
649 std::env::set_var("COGNEE_RESPONSES_CLIENT_ENABLED", "1");
650 std::env::set_var("COGNEE_DISABLE_DEFAULT_BACKENDS", "true");
651 }
652 let cfg = HttpServerConfig::from_env().expect("from_env");
653 unsafe {
655 std::env::remove_var("COGNEE_NOTEBOOK_RUNNER_ENABLED");
656 std::env::remove_var("COGNEE_RESPONSES_CLIENT_ENABLED");
657 std::env::remove_var("COGNEE_DISABLE_DEFAULT_BACKENDS");
658 }
659
660 assert!(cfg.notebook_runner_enabled);
661 assert!(cfg.responses_client_enabled);
662 assert!(cfg.disable_default_backends);
663 }
664
665 #[test]
666 fn test_llm_fallback_env_aliases() {
667 unsafe {
669 std::env::set_var("OPENAI_TOKEN", "test-key");
670 std::env::set_var("OPENAI_MODEL", "gpt-test");
671 std::env::set_var("OPENAI_URL", "https://example.test/v1");
672 std::env::remove_var("LLM_API_KEY");
673 std::env::remove_var("LLM_MODEL");
674 std::env::remove_var("LLM_ENDPOINT");
675 }
676 let cfg = HttpServerConfig::from_env().expect("from_env");
677 unsafe {
679 std::env::remove_var("OPENAI_TOKEN");
680 std::env::remove_var("OPENAI_MODEL");
681 std::env::remove_var("OPENAI_URL");
682 }
683
684 assert_eq!(cfg.llm_api_key.expose_secret(), "test-key");
685 assert_eq!(cfg.llm_model, "gpt-test");
686 assert_eq!(cfg.llm_endpoint, "https://example.test/v1");
687 }
688
689 #[test]
690 fn test_system_root_directory_rebases_dependent_defaults() {
691 let temp = tempfile::tempdir().expect("tempdir");
692 let new_root = temp.path().join("custom-system-root");
693
694 unsafe {
696 std::env::set_var("SYSTEM_ROOT_DIRECTORY", &new_root);
697 std::env::remove_var("RELATIONAL_DB_URL");
698 std::env::remove_var("DATABASE_URL");
699 std::env::remove_var("GRAPH_FILE_PATH");
700 std::env::remove_var("VECTOR_DB_URL");
701 std::env::remove_var("COGNEE_SESSION_DIR");
702 }
703
704 let cfg = HttpServerConfig::from_env().expect("from_env");
705
706 unsafe {
708 std::env::remove_var("SYSTEM_ROOT_DIRECTORY");
709 }
710
711 assert_eq!(cfg.system_root_directory, new_root);
712 assert_eq!(cfg.relational_db_url, default_relational_db_url(&new_root));
713 assert_eq!(cfg.graph_file_path, default_graph_file_path(&new_root));
714 assert_eq!(cfg.vector_db_url, default_vector_db_url(&new_root));
715 assert_eq!(
716 cfg.session_root_directory,
717 default_session_root_directory(&new_root)
718 );
719 }
720}