agent_first_pay/
config.rs1use crate::types::*;
2use agent_first_data::cli_parse_log_filters;
3use std::path::Path;
4
5pub const VERSION: &str = env!("CARGO_PKG_VERSION");
6
7pub fn enabled_features() -> Vec<&'static str> {
8 [
9 #[cfg(feature = "redb")]
10 Some("redb"),
11 #[cfg(feature = "postgres")]
12 Some("postgres"),
13 #[cfg(feature = "cashu")]
14 Some("cashu"),
15 #[cfg(feature = "ln-nwc")]
16 Some("ln-nwc"),
17 #[cfg(feature = "ln-phoenixd")]
18 Some("ln-phoenixd"),
19 #[cfg(feature = "ln-lnbits")]
20 Some("ln-lnbits"),
21 #[cfg(feature = "sol")]
22 Some("sol"),
23 #[cfg(feature = "evm")]
24 Some("evm"),
25 #[cfg(feature = "btc-esplora")]
26 Some("btc-esplora"),
27 #[cfg(feature = "btc-core")]
28 Some("btc-core"),
29 #[cfg(feature = "btc-electrum")]
30 Some("btc-electrum"),
31 #[cfg(feature = "mcp")]
32 Some("mcp"),
33 #[cfg(feature = "interactive")]
34 Some("interactive"),
35 #[cfg(feature = "rest")]
36 Some("rest"),
37 ]
38 .into_iter()
39 .flatten()
40 .collect()
41}
42
43pub fn build_startup_log(
45 argv: Option<Vec<String>>,
46 config: Option<&RuntimeConfig>,
47 args: serde_json::Value,
48) -> Output {
49 Output::Log {
50 event: "startup".to_string(),
51 request_id: None,
52 version: Some(VERSION.to_string()),
53 argv,
54 config: config.map(|c| serde_json::to_value(c).unwrap_or(serde_json::Value::Null)),
55 args: Some(args),
56 env: Some(serde_json::json!({
57 "features": enabled_features(),
58 })),
59 trace: Trace::from_duration(0),
60 }
61}
62
63pub fn should_emit_startup_log(log_filters: &[String], startup_requested: bool) -> bool {
66 startup_requested || !log_filters.is_empty()
67}
68
69pub fn maybe_startup_log(
71 log_filters: &[String],
72 startup_requested: bool,
73 argv: Option<Vec<String>>,
74 config: Option<&RuntimeConfig>,
75 args: serde_json::Value,
76) -> Option<Output> {
77 if !should_emit_startup_log(log_filters, startup_requested) {
78 return None;
79 }
80 Some(build_startup_log(argv, config, args))
81}
82
83impl RuntimeConfig {
84 pub fn load_from_dir(data_dir: &str) -> Result<Self, String> {
86 let path = Path::new(data_dir).join("config.toml");
87 if !path.exists() {
88 return Ok(Self {
89 data_dir: data_dir.to_string(),
90 ..Self::default()
91 });
92 }
93 let contents =
94 std::fs::read_to_string(&path).map_err(|e| format!("read {}: {e}", path.display()))?;
95 let mut cfg: Self =
96 toml::from_str(&contents).map_err(|e| format!("parse {}: {e}", path.display()))?;
97 cfg.data_dir = data_dir.to_string();
99 Ok(cfg)
100 }
101
102 #[allow(dead_code)]
103 pub fn apply_update(&mut self, patch: ConfigPatch) {
104 if let Some(v) = patch.data_dir {
105 self.data_dir = v;
106 }
107 if let Some(v) = patch.limits {
108 self.limits = v;
109 }
110 if let Some(v) = patch.log {
111 self.log = cli_parse_log_filters(&v);
112 }
113 if let Some(rpc_nodes) = patch.afpay_rpc {
114 for (name, cfg) in rpc_nodes {
115 self.afpay_rpc.insert(name, cfg);
116 }
117 }
118 if let Some(providers) = patch.providers {
119 for (network, rpc_name) in providers {
120 self.providers.insert(network, rpc_name);
121 }
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn maybe_startup_log_disabled_without_filters_or_request() {
132 let out = maybe_startup_log(&[], false, None, None, serde_json::json!({"mode": "test"}));
133 assert!(out.is_none());
134 }
135
136 #[test]
137 fn maybe_startup_log_enabled_with_filters() {
138 let filters = vec!["cashu".to_string()];
139 let out = maybe_startup_log(
140 &filters,
141 false,
142 None,
143 None,
144 serde_json::json!({"mode": "test"}),
145 );
146 assert!(out.is_some());
147 }
148
149 #[test]
150 fn maybe_startup_log_enabled_with_explicit_request() {
151 let out = maybe_startup_log(&[], true, None, None, serde_json::json!({"mode": "test"}));
152 assert!(out.is_some());
153 }
154}