Skip to main content

agentic_core/
config.rs

1use std::time::Duration;
2
3pub const DEFAULT_POSTGRES_MAX_CONNECTIONS: u32 = 10;
4pub const DEFAULT_POSTGRES_ACQUIRE_TIMEOUT_SECONDS: u64 = 30;
5pub const DEFAULT_POSTGRES_IDLE_TIMEOUT_SECONDS: u64 = 600;
6pub const DEFAULT_POSTGRES_LOCK_TIMEOUT_SECONDS: u64 = 5;
7pub const DEFAULT_POSTGRES_MAX_LIFETIME_SECONDS: u64 = 1_800;
8pub const DEFAULT_POSTGRES_MIGRATION_TIMEOUT_SECONDS: u64 = 300;
9pub const DEFAULT_POSTGRES_STATEMENT_TIMEOUT_SECONDS: u64 = 30;
10pub const DEFAULT_SQLITE_MAX_CONNECTIONS: u32 = 4;
11pub const DEFAULT_SQLITE_JOURNAL_SIZE_LIMIT_BYTES: u64 = 6_144_000;
12pub const DEFAULT_SQLITE_MMAP_SIZE_BYTES: u64 = 268_435_456;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct PostgresConfig {
16    pub max_connections: u32,
17    pub acquire_timeout: Duration,
18    pub lock_timeout: Duration,
19    pub migration_timeout: Duration,
20    pub statement_timeout: Duration,
21    pub idle_timeout: Option<Duration>,
22    pub max_lifetime: Option<Duration>,
23}
24
25impl Default for PostgresConfig {
26    fn default() -> Self {
27        Self {
28            max_connections: DEFAULT_POSTGRES_MAX_CONNECTIONS,
29            acquire_timeout: Duration::from_secs(DEFAULT_POSTGRES_ACQUIRE_TIMEOUT_SECONDS),
30            lock_timeout: Duration::from_secs(DEFAULT_POSTGRES_LOCK_TIMEOUT_SECONDS),
31            migration_timeout: Duration::from_secs(DEFAULT_POSTGRES_MIGRATION_TIMEOUT_SECONDS),
32            statement_timeout: Duration::from_secs(DEFAULT_POSTGRES_STATEMENT_TIMEOUT_SECONDS),
33            idle_timeout: Some(Duration::from_secs(DEFAULT_POSTGRES_IDLE_TIMEOUT_SECONDS)),
34            max_lifetime: Some(Duration::from_secs(DEFAULT_POSTGRES_MAX_LIFETIME_SECONDS)),
35        }
36    }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum SqliteTempStore {
41    Default,
42    File,
43    #[default]
44    Memory,
45}
46
47impl SqliteTempStore {
48    #[must_use]
49    pub fn as_pragma_value(self) -> &'static str {
50        match self {
51            Self::Default => "DEFAULT",
52            Self::File => "FILE",
53            Self::Memory => "MEMORY",
54        }
55    }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct SqliteConfig {
60    pub max_connections: u32,
61    pub journal_size_limit_bytes: u64,
62    pub temp_store: SqliteTempStore,
63    pub mmap_size_bytes: u64,
64}
65
66impl Default for SqliteConfig {
67    fn default() -> Self {
68        Self {
69            max_connections: DEFAULT_SQLITE_MAX_CONNECTIONS,
70            journal_size_limit_bytes: DEFAULT_SQLITE_JOURNAL_SIZE_LIMIT_BYTES,
71            temp_store: SqliteTempStore::default(),
72            mmap_size_bytes: DEFAULT_SQLITE_MMAP_SIZE_BYTES,
73        }
74    }
75}
76
77#[derive(Debug, Clone)]
78pub struct Config {
79    pub llm_api_base: String,
80    pub openai_api_key: Option<String>,
81    pub llm_ready_timeout_s: f64,
82    pub llm_ready_interval_s: f64,
83    pub skip_llm_ready_check: bool,
84    /// Database URL for conversation and response storage.
85    /// `None` means stateful features are disabled; all requests are proxied.
86    pub db_url: Option<String>,
87    pub postgres: PostgresConfig,
88    pub sqlite: SqliteConfig,
89}
90
91#[must_use]
92pub fn normalize_base_url(url: &str) -> String {
93    let mut s = url.trim_end_matches('/').to_owned();
94    if s.ends_with("/v1") {
95        s.truncate(s.len() - 3);
96        s = s.trim_end_matches('/').to_owned();
97    }
98    s
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn strip_trailing_v1() {
107        assert_eq!(normalize_base_url("http://host:8000/v1"), "http://host:8000");
108        assert_eq!(normalize_base_url("http://host:8000/v1/"), "http://host:8000");
109    }
110
111    #[test]
112    fn no_v1_unchanged() {
113        assert_eq!(normalize_base_url("http://host:8000"), "http://host:8000");
114        assert_eq!(normalize_base_url("http://host:8000/"), "http://host:8000");
115    }
116}