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
27#[cfg(feature = "sqlite")]
28use std::path::PathBuf;
29#[cfg(feature = "sqlite")]
30use std::time::Duration;
31
32#[cfg(feature = "sqlite")]
33const DEFAULT_SQL_BUSY_TIMEOUT_MS: u64 = 5000;
34#[cfg(feature = "sqlite")]
35const DEFAULT_SQL_QUERY_TIMEOUT_MS: u64 = 5000;
36#[cfg(feature = "sqlite")]
37const DEFAULT_SQL_JOURNAL_MODE: &str = "WAL";
38const DEFAULT_BUS_CAPACITY: usize = 64;
39const DEFAULT_TASK_GRACE_MS: u64 = 1000;
40
41/// Base dir for agent-block local state.
42/// `AGENT_BLOCK_HOME` → `$HOME/.agent-block`.
43#[cfg(feature = "sqlite")]
44pub fn base_dir() -> Result<PathBuf, String> {
45 if let Some(v) = std::env::var_os("AGENT_BLOCK_HOME") {
46 return Ok(PathBuf::from(v));
47 }
48 let home = std::env::var_os("HOME").ok_or_else(|| "HOME env var not set".to_string())?;
49 Ok(PathBuf::from(home).join(".agent-block"))
50}
51
52/// Path to the std.kv SQLite database file (or `:memory:`).
53/// `AGENT_BLOCK_KV_PATH` → `{base_dir}/kv.sqlite`.
54#[cfg(feature = "sqlite")]
55pub fn kv_path() -> Result<PathBuf, String> {
56 if let Some(v) = std::env::var_os("AGENT_BLOCK_KV_PATH") {
57 return Ok(PathBuf::from(v));
58 }
59 Ok(base_dir()?.join("kv.sqlite"))
60}
61
62/// Path to the std.sql SQLite database file (or `:memory:`).
63/// `AGENT_BLOCK_SQL_PATH` → `{base_dir}/db.sqlite`.
64#[cfg(feature = "sqlite")]
65pub fn sql_path() -> Result<PathBuf, String> {
66 if let Some(v) = std::env::var_os("AGENT_BLOCK_SQL_PATH") {
67 return Ok(PathBuf::from(v));
68 }
69 Ok(base_dir()?.join("db.sqlite"))
70}
71
72/// Path to the std.ts SQLite database file (or `:memory:`).
73///
74/// `AGENT_BLOCK_TS_PATH` → `{base_dir}/ts.sqlite`.
75/// Separate from kv and sql so the TSDB WAL does not share page cache or
76/// backup lifecycle with agent-internal KV or user SQL data.
77#[cfg(feature = "sqlite")]
78pub fn ts_path() -> Result<PathBuf, String> {
79 if let Some(v) = std::env::var_os("AGENT_BLOCK_TS_PATH") {
80 return Ok(PathBuf::from(v));
81 }
82 Ok(base_dir()?.join("ts.sqlite"))
83}
84
85/// True when the resolved path is SQLite's in-memory sentinel.
86#[cfg(feature = "sqlite")]
87pub fn is_memory_sql(path: &std::path::Path) -> bool {
88 path.as_os_str() == ":memory:"
89}
90
91/// SQLite busy_timeout.
92/// `AGENT_BLOCK_SQL_BUSY_TIMEOUT_MS` → 5000ms.
93#[cfg(feature = "sqlite")]
94pub fn sql_busy_timeout() -> Duration {
95 let ms = std::env::var("AGENT_BLOCK_SQL_BUSY_TIMEOUT_MS")
96 .ok()
97 .and_then(|s| s.parse::<u64>().ok())
98 .unwrap_or(DEFAULT_SQL_BUSY_TIMEOUT_MS);
99 Duration::from_millis(ms)
100}
101
102/// SQLite journal_mode pragma value.
103/// `AGENT_BLOCK_SQL_JOURNAL_MODE` → `WAL`.
104#[cfg(feature = "sqlite")]
105pub fn sql_journal_mode() -> String {
106 std::env::var("AGENT_BLOCK_SQL_JOURNAL_MODE")
107 .unwrap_or_else(|_| DEFAULT_SQL_JOURNAL_MODE.to_string())
108}
109
110/// Per-query timeout. `0` disables the timeout.
111/// `AGENT_BLOCK_SQL_QUERY_TIMEOUT_MS` → 5000ms.
112#[cfg(feature = "sqlite")]
113pub fn sql_query_timeout() -> Option<Duration> {
114 let ms = std::env::var("AGENT_BLOCK_SQL_QUERY_TIMEOUT_MS")
115 .ok()
116 .and_then(|s| s.parse::<u64>().ok())
117 .unwrap_or(DEFAULT_SQL_QUERY_TIMEOUT_MS);
118 if ms == 0 {
119 None
120 } else {
121 Some(Duration::from_millis(ms))
122 }
123}
124
125/// EventBus bounded mpsc capacity.
126/// `AGENT_BLOCK_BUS_CAPACITY` → 64. Parse failures warn and fall back.
127pub fn bus_capacity() -> usize {
128 match std::env::var("AGENT_BLOCK_BUS_CAPACITY") {
129 Ok(v) => v.parse::<usize>().unwrap_or_else(|e| {
130 tracing::warn!(
131 value = %v,
132 error = %e,
133 default = DEFAULT_BUS_CAPACITY,
134 "AGENT_BLOCK_BUS_CAPACITY parse failed, using default"
135 );
136 DEFAULT_BUS_CAPACITY
137 }),
138 Err(_) => DEFAULT_BUS_CAPACITY,
139 }
140}
141
142/// SIGTERM/SIGINT grace window (ms) shared by `std.task.with_timeout` and the
143/// EventBus shutdown path.
144/// `AGENT_BLOCK_TASK_GRACE_MS` → 1000. Parse failures warn and fall back.
145pub fn task_grace_ms() -> u64 {
146 match std::env::var("AGENT_BLOCK_TASK_GRACE_MS") {
147 Ok(v) => v.parse::<u64>().unwrap_or_else(|e| {
148 tracing::warn!(
149 value = %v,
150 error = %e,
151 default = DEFAULT_TASK_GRACE_MS,
152 "AGENT_BLOCK_TASK_GRACE_MS parse failed, using default"
153 );
154 DEFAULT_TASK_GRACE_MS
155 }),
156 Err(_) => DEFAULT_TASK_GRACE_MS,
157 }
158}