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