algocline_app/service/
mod.rs1pub(crate) mod alc_toml;
2mod card;
3mod config;
4mod engine_api_impl;
5mod eval;
6mod eval_store;
7mod init;
8pub(crate) mod lockfile;
9mod logging;
10pub(crate) mod manifest;
11mod migrate;
12pub(crate) mod path;
13mod pkg;
14mod pkg_link;
15mod pkg_unlink;
16pub(crate) mod project;
17pub mod resolve;
18mod run;
19mod scenario;
20pub(crate) mod source;
21mod status;
22mod transcript;
23mod update;
24
25#[cfg(test)]
26mod test_support;
27#[cfg(test)]
28mod tests;
29
30use std::path::Path;
31use std::sync::Arc;
32
33use algocline_engine::{Executor, SessionRegistry};
34
35pub use algocline_core::{EngineApi, TokenUsage};
36pub use config::{AppConfig, LogDirSource};
37pub use resolve::{QueryResponse, SearchPath};
38
39type EvalSessions = std::sync::Mutex<std::collections::HashMap<String, String>>;
52
53type SessionStrategies = std::sync::Mutex<std::collections::HashMap<String, String>>;
59
60#[derive(Clone)]
61pub struct AppService {
62 executor: Arc<Executor>,
63 registry: Arc<SessionRegistry>,
64 log_config: AppConfig,
65 search_paths: Vec<resolve::SearchPath>,
67 eval_sessions: Arc<EvalSessions>,
69 session_strategies: Arc<SessionStrategies>,
71}
72
73impl AppService {
74 pub fn new(
75 executor: Arc<Executor>,
76 log_config: AppConfig,
77 search_paths: Vec<resolve::SearchPath>,
78 ) -> Self {
79 let registry = Arc::new(SessionRegistry::new());
80 registry.spawn_gc_task(std::time::Duration::from_secs(10800));
83 Self {
84 executor,
85 registry,
86 log_config,
87 search_paths,
88 eval_sessions: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
89 session_strategies: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
90 }
91 }
92
93 fn require_log_dir(&self) -> Result<&Path, String> {
95 self.log_config
96 .log_dir
97 .as_deref()
98 .ok_or_else(|| "File logging is not available (no writable log directory)".to_string())
99 }
100
101 pub(crate) fn resolve_extra_lib_paths(
111 &self,
112 project_root: Option<&str>,
113 ) -> Vec<std::path::PathBuf> {
114 let Some(root) = project::resolve_project_root(project_root) else {
115 return vec![];
116 };
117
118 match lockfile::load_lockfile(&root) {
119 Ok(Some(lock)) => {
120 self.warn_toml_lock_mismatch(&root, &lock);
121 lockfile::resolve_path_entries(&root, &lock)
122 }
123 Ok(None) => vec![],
124 Err(e) => {
125 tracing::warn!("failed to load alc.lock at {}: {e}", root.display());
126 vec![]
127 }
128 }
129 }
130
131 fn warn_toml_lock_mismatch(&self, root: &Path, lock: &lockfile::LockFile) {
132 let toml = match alc_toml::load_alc_toml(root) {
133 Ok(Some(t)) => t,
134 _ => return,
135 };
136
137 use std::collections::BTreeSet;
138 let toml_names: BTreeSet<&str> = toml.packages.keys().map(|s| s.as_str()).collect();
139 let lock_names: BTreeSet<&str> = lock.packages.iter().map(|p| p.name.as_str()).collect();
140
141 for name in toml_names.difference(&lock_names) {
142 eprintln!(
143 "warning: '{name}' is declared in alc.toml but missing from alc.lock. Run `alc_update` to sync."
144 );
145 }
146 for name in lock_names.difference(&toml_names) {
147 eprintln!("warning: '{name}' is in alc.lock but not declared in alc.toml.");
148 }
149 }
150}