algocline_app/service/
mod.rs1pub(crate) mod alc_toml;
2mod card;
3mod config;
4mod engine_api_impl;
5mod eval;
6mod eval_store;
7mod hub;
8mod init;
9pub(crate) mod list_opts;
10pub(crate) mod lock;
11pub(crate) mod lockfile;
12mod logging;
13pub(crate) mod manifest;
14mod migrate;
15pub(crate) mod path;
16mod pkg;
17mod pkg_link;
18mod pkg_unlink;
19pub(crate) mod project;
20pub mod resolve;
21mod run;
22mod scenario;
23pub(crate) mod source;
24mod status;
25mod transcript;
26mod update;
27
28#[cfg(test)]
29mod test_support;
30#[cfg(test)]
31mod tests;
32
33use std::path::Path;
34use std::sync::Arc;
35
36use algocline_engine::{Executor, SessionRegistry, VariantPkg};
37
38pub use algocline_core::{EngineApi, TokenUsage};
39pub use config::{AppConfig, LogDirSource};
40pub use resolve::{QueryResponse, SearchPath};
41
42type EvalSessions = std::sync::Mutex<std::collections::HashMap<String, String>>;
55
56type SessionStrategies = std::sync::Mutex<std::collections::HashMap<String, String>>;
62
63#[derive(Clone)]
64pub struct AppService {
65 executor: Arc<Executor>,
66 registry: Arc<SessionRegistry>,
67 log_config: AppConfig,
68 search_paths: Vec<resolve::SearchPath>,
70 eval_sessions: Arc<EvalSessions>,
72 session_strategies: Arc<SessionStrategies>,
74}
75
76impl AppService {
77 pub fn new(
78 executor: Arc<Executor>,
79 log_config: AppConfig,
80 search_paths: Vec<resolve::SearchPath>,
81 ) -> Self {
82 let registry = Arc::new(SessionRegistry::new());
83 registry.spawn_gc_task(std::time::Duration::from_secs(10800));
86 Self {
87 executor,
88 registry,
89 log_config,
90 search_paths,
91 eval_sessions: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
92 session_strategies: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
93 }
94 }
95
96 fn require_log_dir(&self) -> Result<&Path, String> {
98 self.log_config
99 .log_dir
100 .as_deref()
101 .ok_or_else(|| "File logging is not available (no writable log directory)".to_string())
102 }
103
104 pub(crate) fn resolve_extra_lib_paths(
117 &self,
118 project_root: Option<&str>,
119 ) -> Vec<std::path::PathBuf> {
120 let Some(root) = project::resolve_project_root(project_root) else {
121 return vec![];
122 };
123
124 let local_paths: Vec<std::path::PathBuf> = match alc_toml::load_alc_local_toml(&root) {
127 Ok(Some(local)) => alc_toml::resolve_local_path_entries(&root, &local),
128 Ok(None) => Vec::new(),
129 Err(e) => {
130 tracing::warn!("failed to load alc.local.toml at {}: {e}", root.display());
131 Vec::new()
132 }
133 };
134
135 let lock_paths: Vec<std::path::PathBuf> = match lockfile::load_lockfile(&root) {
137 Ok(Some(lock)) => {
138 self.warn_toml_lock_mismatch(&root, &lock);
139 lockfile::resolve_path_entries(&root, &lock)
140 }
141 Ok(None) => Vec::new(),
142 Err(e) => {
143 tracing::warn!("failed to load alc.lock at {}: {e}", root.display());
144 Vec::new()
145 }
146 };
147
148 let mut merged = local_paths;
149 merged.extend(lock_paths);
150 merged
151 }
152
153 pub(crate) fn resolve_variant_pkgs(&self, project_root: Option<&str>) -> Vec<VariantPkg> {
161 let Some(root) = project::resolve_project_root(project_root) else {
162 return vec![];
163 };
164
165 match alc_toml::load_alc_local_toml(&root) {
166 Ok(Some(local)) => alc_toml::resolve_local_variant_pkgs(&root, &local),
167 Ok(None) => Vec::new(),
168 Err(e) => {
169 tracing::warn!("failed to load alc.local.toml at {}: {e}", root.display());
170 Vec::new()
171 }
172 }
173 }
174
175 fn warn_toml_lock_mismatch(&self, root: &Path, lock: &lockfile::LockFile) {
176 let toml = match alc_toml::load_alc_toml(root) {
177 Ok(Some(t)) => t,
178 _ => return,
179 };
180
181 use std::collections::BTreeSet;
182 let toml_names: BTreeSet<&str> = toml.packages.keys().map(|s| s.as_str()).collect();
183 let lock_names: BTreeSet<&str> = lock.packages.iter().map(|p| p.name.as_str()).collect();
184
185 for name in toml_names.difference(&lock_names) {
186 eprintln!(
187 "warning: '{name}' is declared in alc.toml but missing from alc.lock. Run `alc_update` to sync."
188 );
189 }
190 for name in lock_names.difference(&toml_names) {
191 eprintln!("warning: '{name}' is in alc.lock but not declared in alc.toml.");
192 }
193 }
194}