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