agent_block_core/bridge/config.rs
1//! Config resolution for `std.kv` / `std.sql` / `std.ts` storage backends.
2//!
3//! All knobs are ENV-driven (no CLI flags) so `.env` can drive them uniformly.
4//!
5//! | ENV var | Default | Used by |
6//! |------------------------------------|--------------------------|----------|
7//! | `AGENT_BLOCK_HOME` | `$HOME/.agent-block` | all |
8//! | `AGENT_BLOCK_KV_PATH` | `{HOME}/kv.sqlite` | std.kv |
9//! | `AGENT_BLOCK_SQL_PATH` | `{HOME}/db.sqlite` | std.sql |
10//! | `AGENT_BLOCK_TS_PATH` | `{HOME}/ts.sqlite` | std.ts |
11//! | `AGENT_BLOCK_SQL_BUSY_TIMEOUT_MS` | `5000` | all |
12//! | `AGENT_BLOCK_SQL_QUERY_TIMEOUT_MS` | `5000` | all |
13//! | `AGENT_BLOCK_SQL_JOURNAL_MODE` | `WAL` | all |
14//! | `AGENT_BLOCK_BUS_CAPACITY` | `64` | EventBus |
15//! | `AGENT_BLOCK_TASK_GRACE_MS` | `1000` | task/bus |
16//!
17//! `std.kv`, `std.sql`, and `std.ts` are backed by separate SQLite database
18//! files so that agent-internal KV state, explicit user SQL data, and
19//! time-series rows don't share WAL, page cache, or backup lifecycle.
20//! Pragma/timeout knobs apply to all three.
21//!
22//! Special: `=:memory:` selects an in-memory database (works for
23//! `AGENT_BLOCK_KV_PATH`, `AGENT_BLOCK_SQL_PATH`, and `AGENT_BLOCK_TS_PATH`).
24//! Journal mode is ignored for `:memory:` (SQLite forces MEMORY).
25//! `AGENT_BLOCK_SQL_QUERY_TIMEOUT_MS=0` disables the query timeout.
26
27use std::path::PathBuf;
28use std::time::Duration;
29
30const DEFAULT_SQL_BUSY_TIMEOUT_MS: u64 = 5000;
31const DEFAULT_SQL_QUERY_TIMEOUT_MS: u64 = 5000;
32const DEFAULT_SQL_JOURNAL_MODE: &str = "WAL";
33const DEFAULT_BUS_CAPACITY: usize = 64;
34const DEFAULT_TASK_GRACE_MS: u64 = 1000;
35
36/// Base dir for agent-block local state.
37/// `AGENT_BLOCK_HOME` → `$HOME/.agent-block`.
38pub fn base_dir() -> Result<PathBuf, String> {
39 if let Some(v) = std::env::var_os("AGENT_BLOCK_HOME") {
40 return Ok(PathBuf::from(v));
41 }
42 let home = std::env::var_os("HOME").ok_or_else(|| "HOME env var not set".to_string())?;
43 Ok(PathBuf::from(home).join(".agent-block"))
44}
45
46/// Path to the std.kv SQLite database file (or `:memory:`).
47/// `AGENT_BLOCK_KV_PATH` → `{base_dir}/kv.sqlite`.
48pub fn kv_path() -> Result<PathBuf, String> {
49 if let Some(v) = std::env::var_os("AGENT_BLOCK_KV_PATH") {
50 return Ok(PathBuf::from(v));
51 }
52 Ok(base_dir()?.join("kv.sqlite"))
53}
54
55/// Path to the std.sql SQLite database file (or `:memory:`).
56/// `AGENT_BLOCK_SQL_PATH` → `{base_dir}/db.sqlite`.
57pub fn sql_path() -> Result<PathBuf, String> {
58 if let Some(v) = std::env::var_os("AGENT_BLOCK_SQL_PATH") {
59 return Ok(PathBuf::from(v));
60 }
61 Ok(base_dir()?.join("db.sqlite"))
62}
63
64/// Path to the std.ts SQLite database file (or `:memory:`).
65///
66/// `AGENT_BLOCK_TS_PATH` → `{base_dir}/ts.sqlite`.
67/// Separate from kv and sql so the TSDB WAL does not share page cache or
68/// backup lifecycle with agent-internal KV or user SQL data.
69pub fn ts_path() -> Result<PathBuf, String> {
70 if let Some(v) = std::env::var_os("AGENT_BLOCK_TS_PATH") {
71 return Ok(PathBuf::from(v));
72 }
73 Ok(base_dir()?.join("ts.sqlite"))
74}
75
76/// True when the resolved path is SQLite's in-memory sentinel.
77pub fn is_memory_sql(path: &std::path::Path) -> bool {
78 path.as_os_str() == ":memory:"
79}
80
81/// SQLite busy_timeout.
82/// `AGENT_BLOCK_SQL_BUSY_TIMEOUT_MS` → 5000ms.
83pub fn sql_busy_timeout() -> Duration {
84 let ms = std::env::var("AGENT_BLOCK_SQL_BUSY_TIMEOUT_MS")
85 .ok()
86 .and_then(|s| s.parse::<u64>().ok())
87 .unwrap_or(DEFAULT_SQL_BUSY_TIMEOUT_MS);
88 Duration::from_millis(ms)
89}
90
91/// SQLite journal_mode pragma value.
92/// `AGENT_BLOCK_SQL_JOURNAL_MODE` → `WAL`.
93pub fn sql_journal_mode() -> String {
94 std::env::var("AGENT_BLOCK_SQL_JOURNAL_MODE")
95 .unwrap_or_else(|_| DEFAULT_SQL_JOURNAL_MODE.to_string())
96}
97
98/// Per-query timeout. `0` disables the timeout.
99/// `AGENT_BLOCK_SQL_QUERY_TIMEOUT_MS` → 5000ms.
100pub fn sql_query_timeout() -> Option<Duration> {
101 let ms = std::env::var("AGENT_BLOCK_SQL_QUERY_TIMEOUT_MS")
102 .ok()
103 .and_then(|s| s.parse::<u64>().ok())
104 .unwrap_or(DEFAULT_SQL_QUERY_TIMEOUT_MS);
105 if ms == 0 {
106 None
107 } else {
108 Some(Duration::from_millis(ms))
109 }
110}
111
112/// EventBus bounded mpsc capacity.
113/// `AGENT_BLOCK_BUS_CAPACITY` → 64. Parse failures warn and fall back.
114pub fn bus_capacity() -> usize {
115 match std::env::var("AGENT_BLOCK_BUS_CAPACITY") {
116 Ok(v) => v.parse::<usize>().unwrap_or_else(|e| {
117 tracing::warn!(
118 value = %v,
119 error = %e,
120 default = DEFAULT_BUS_CAPACITY,
121 "AGENT_BLOCK_BUS_CAPACITY parse failed, using default"
122 );
123 DEFAULT_BUS_CAPACITY
124 }),
125 Err(_) => DEFAULT_BUS_CAPACITY,
126 }
127}
128
129/// SIGTERM/SIGINT grace window (ms) shared by `std.task.with_timeout` and the
130/// EventBus shutdown path.
131/// `AGENT_BLOCK_TASK_GRACE_MS` → 1000. Parse failures warn and fall back.
132pub fn task_grace_ms() -> u64 {
133 match std::env::var("AGENT_BLOCK_TASK_GRACE_MS") {
134 Ok(v) => v.parse::<u64>().unwrap_or_else(|e| {
135 tracing::warn!(
136 value = %v,
137 error = %e,
138 default = DEFAULT_TASK_GRACE_MS,
139 "AGENT_BLOCK_TASK_GRACE_MS parse failed, using default"
140 );
141 DEFAULT_TASK_GRACE_MS
142 }),
143 Err(_) => DEFAULT_TASK_GRACE_MS,
144 }
145}