1use std::sync::Arc;
2
3use algocline_core::QueryId;
4use algocline_engine::{FeedResult, VariantPkg};
5
6use super::eval_store::splice_response_string;
7use super::resolve::{is_package_installed, make_require_code, resolve_code, QueryResponse};
8use super::transcript::write_transcript_log;
9use super::AppService;
10
11fn splice_save_warning(result_json: &str, warning: Option<String>) -> String {
15 match warning {
16 Some(msg) => splice_response_string(result_json, "save_warning", &msg),
17 None => result_json.to_string(),
18 }
19}
20
21impl AppService {
22 pub async fn run(
27 &self,
28 code: Option<String>,
29 code_file: Option<String>,
30 ctx: Option<serde_json::Value>,
31 project_root: Option<String>,
32 ) -> Result<String, String> {
33 let code = resolve_code(code, code_file)?;
34 let ctx = ctx.unwrap_or(serde_json::Value::Null);
35 let extra = self.resolve_extra_lib_paths(project_root.as_deref());
36 let variants = self.resolve_variant_pkgs(project_root.as_deref());
37 self.start_and_tick(code, ctx, None, extra, variants).await
38 }
39
40 pub async fn advice(
48 &self,
49 strategy: &str,
50 task: Option<String>,
51 opts: Option<serde_json::Value>,
52 project_root: Option<String>,
53 ) -> Result<String, String> {
54 let app_dir = self.log_config.app_dir();
56 if !is_package_installed(&app_dir, strategy) {
57 self.auto_install_bundled_packages().await?;
58 if !is_package_installed(&app_dir, strategy) {
59 return Err(format!(
60 "Package '{strategy}' not found after installing bundled collection. \
61 Use alc_pkg_install to install it manually."
62 ));
63 }
64 }
65
66 let code = make_require_code(strategy);
67
68 let mut ctx_map = match opts {
69 Some(serde_json::Value::Object(m)) => m,
70 _ => serde_json::Map::new(),
71 };
72 if let Some(task) = task {
73 ctx_map.insert("task".into(), serde_json::Value::String(task));
74 }
75 let ctx = serde_json::Value::Object(ctx_map);
76
77 let extra = self.resolve_extra_lib_paths(project_root.as_deref());
78 let variants = self.resolve_variant_pkgs(project_root.as_deref());
79 self.start_and_tick(code, ctx, Some(strategy), extra, variants)
80 .await
81 }
82
83 pub async fn continue_batch(
85 &self,
86 session_id: &str,
87 responses: Vec<QueryResponse>,
88 ) -> Result<String, String> {
89 let mut last_result = None;
90 for qr in responses {
91 let qid = QueryId::parse(&qr.query_id);
92 let result = self
93 .registry
94 .feed_response(session_id, &qid, qr.response, qr.usage.as_ref())
95 .await
96 .map_err(|e| format!("Continue failed: {e}"))?;
97 last_result = Some(result);
98 }
99 let result = last_result.ok_or("Empty responses array")?;
100 self.maybe_log_transcript(&result, session_id);
101 let json = result.to_json(session_id).to_string();
102 let save_warning = self.maybe_save_eval(&result, session_id, &json);
103 Ok(splice_save_warning(&json, save_warning))
104 }
105
106 pub async fn continue_single(
108 &self,
109 session_id: &str,
110 response: String,
111 query_id: Option<&str>,
112 usage: Option<algocline_core::TokenUsage>,
113 ) -> Result<String, String> {
114 let query_id = match query_id {
115 Some(qid) => QueryId::parse(qid),
116 None => self
117 .registry
118 .resolve_sole_pending_id(session_id)
119 .await
120 .map_err(|e| format!("Continue failed: {e}"))?,
121 };
122
123 let result = self
124 .registry
125 .feed_response(session_id, &query_id, response, usage.as_ref())
126 .await
127 .map_err(|e| format!("Continue failed: {e}"))?;
128
129 self.maybe_log_transcript(&result, session_id);
130 let json = result.to_json(session_id).to_string();
131 let save_warning = self.maybe_save_eval(&result, session_id, &json);
132 Ok(splice_save_warning(&json, save_warning))
133 }
134
135 pub(super) fn maybe_log_transcript(&self, result: &FeedResult, session_id: &str) {
138 if let FeedResult::Finished(exec_result) = result {
139 let strategy = self
140 .session_strategies
141 .lock()
142 .ok()
143 .and_then(|mut map| map.remove(session_id));
144 write_transcript_log(
145 &self.log_config,
146 session_id,
147 &exec_result.metrics,
148 strategy.as_deref(),
149 );
150 }
151 }
152
153 pub(super) fn maybe_save_eval(
159 &self,
160 result: &FeedResult,
161 session_id: &str,
162 result_json: &str,
163 ) -> Option<String> {
164 if !matches!(result, FeedResult::Finished(_)) {
165 return None;
166 }
167 let strategy = {
168 let mut map = self.eval_sessions.lock().unwrap_or_else(|e| e.into_inner());
169 map.remove(session_id)
170 };
171 strategy.and_then(|s| {
172 super::eval_store::save_eval_result(&self.log_config.app_dir(), &s, result_json).err()
173 })
174 }
175
176 pub(super) async fn start_and_tick(
177 &self,
178 code: String,
179 ctx: serde_json::Value,
180 strategy: Option<&str>,
181 extra_lib_paths: Vec<std::path::PathBuf>,
182 variant_pkgs: Vec<VariantPkg>,
183 ) -> Result<String, String> {
184 let scenarios_dir = self.log_config.app_dir().scenarios_dir();
185 let session = self
186 .executor
187 .start_session(
188 code,
189 ctx,
190 extra_lib_paths,
191 variant_pkgs,
192 Arc::clone(&self.state_store),
193 Arc::clone(&self.card_store),
194 scenarios_dir,
195 )
196 .await?;
197 let (session_id, result) = self
198 .registry
199 .start_execution(session)
200 .await
201 .map_err(|e| format!("Execution failed: {e}"))?;
202 if let Some(s) = strategy {
203 if let Ok(mut map) = self.session_strategies.lock() {
204 map.insert(session_id.clone(), s.to_string());
205 }
206 }
207 self.maybe_log_transcript(&result, &session_id);
208 Ok(result.to_json(&session_id).to_string())
209 }
210}