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