1use std::collections::HashMap;
2
3use algocline_core::{EngineApi, QueryResponse};
4use async_trait::async_trait;
5
6use super::list_opts::ListOpts;
7use super::AppService;
8
9#[async_trait]
16impl EngineApi for AppService {
17 async fn run(
20 &self,
21 code: Option<String>,
22 code_file: Option<String>,
23 ctx: Option<serde_json::Value>,
24 project_root: Option<String>,
25 ) -> Result<String, String> {
26 AppService::run(self, code, code_file, ctx, project_root).await
27 }
28
29 async fn advice(
30 &self,
31 strategy: &str,
32 task: Option<String>,
33 opts: Option<serde_json::Value>,
34 project_root: Option<String>,
35 ) -> Result<String, String> {
36 AppService::advice(self, strategy, task, opts, project_root).await
37 }
38
39 async fn continue_single(
40 &self,
41 session_id: &str,
42 response: String,
43 query_id: Option<&str>,
44 usage: Option<algocline_core::TokenUsage>,
45 ) -> Result<String, String> {
46 AppService::continue_single(self, session_id, response, query_id, usage).await
47 }
48
49 async fn continue_batch(
50 &self,
51 session_id: &str,
52 responses: Vec<QueryResponse>,
53 ) -> Result<String, String> {
54 AppService::continue_batch(self, session_id, responses).await
55 }
56
57 async fn status(
60 &self,
61 session_id: Option<&str>,
62 pending_filter: Option<serde_json::Value>,
63 include_history: bool,
64 ) -> Result<String, String> {
65 AppService::status(self, session_id, pending_filter, include_history).await
66 }
67
68 async fn eval(
71 &self,
72 scenario: Option<String>,
73 scenario_file: Option<String>,
74 scenario_name: Option<String>,
75 strategy: &str,
76 strategy_opts: Option<serde_json::Value>,
77 auto_card: bool,
78 ) -> Result<String, String> {
79 AppService::eval(
80 self,
81 scenario,
82 scenario_file,
83 scenario_name,
84 strategy,
85 strategy_opts,
86 auto_card,
87 )
88 .await
89 }
90
91 async fn eval_history(&self, strategy: Option<&str>, limit: usize) -> Result<String, String> {
92 AppService::eval_history(self, strategy, limit)
93 }
94
95 async fn eval_detail(&self, eval_id: &str) -> Result<String, String> {
96 AppService::eval_detail(self, eval_id)
97 }
98
99 async fn eval_compare(&self, eval_id_a: &str, eval_id_b: &str) -> Result<String, String> {
100 AppService::eval_compare(self, eval_id_a, eval_id_b).await
101 }
102
103 async fn scenario_list(&self) -> Result<String, String> {
106 AppService::scenario_list(self)
107 }
108
109 async fn scenario_show(&self, name: &str) -> Result<String, String> {
110 AppService::scenario_show(self, name)
111 }
112
113 async fn scenario_install(&self, url: String) -> Result<String, String> {
114 AppService::scenario_install(self, url).await
115 }
116
117 async fn pkg_link(
120 &self,
121 path: String,
122 name: Option<String>,
123 force: Option<bool>,
124 scope: Option<String>,
125 project_root: Option<String>,
126 ) -> Result<String, String> {
127 AppService::pkg_link(self, path, name, force, scope, project_root).await
128 }
129
130 async fn pkg_unlink(&self, name: String) -> Result<String, String> {
131 AppService::pkg_unlink(self, name).await
132 }
133
134 #[allow(clippy::too_many_arguments)]
135 async fn pkg_list(
136 &self,
137 project_root: Option<String>,
138 limit: Option<i32>,
139 sort: Option<String>,
140 filter: Option<serde_json::Value>,
141 fields: Option<Vec<String>>,
142 verbose: Option<String>,
143 ) -> Result<String, String> {
144 let filter_map = match filter {
150 None => None,
151 Some(v) => match serde_json::from_value::<HashMap<String, serde_json::Value>>(v) {
152 Ok(map) => Some(map),
153 Err(e) => {
154 tracing::warn!(error = %e, "pkg_list: filter value is not a JSON object — treating as no filter");
155 None
156 }
157 },
158 };
159
160 let opts = ListOpts {
165 limit: limit.map(|n| n.max(0) as usize),
166 sort,
167 filter: filter_map,
168 fields,
169 verbose,
170 };
171
172 AppService::pkg_list(self, project_root, opts)
173 .await
174 .map_err(|e| e.to_string())
175 }
176
177 async fn pkg_install(&self, url: String, name: Option<String>) -> Result<String, String> {
178 AppService::pkg_install(self, url, name).await
179 }
180
181 async fn pkg_remove(
182 &self,
183 name: &str,
184 project_root: Option<String>,
185 version: Option<String>,
186 scope: Option<String>,
187 ) -> Result<String, String> {
188 AppService::pkg_remove(self, name, project_root, version, scope).await
189 }
190
191 async fn pkg_repair(
192 &self,
193 name: Option<String>,
194 project_root: Option<String>,
195 ) -> Result<String, String> {
196 AppService::pkg_repair(self, name, project_root).await
197 }
198
199 async fn pkg_doctor(
200 &self,
201 name: Option<String>,
202 project_root: Option<String>,
203 ) -> Result<String, String> {
204 AppService::pkg_doctor(self, name, project_root).await
205 }
206
207 async fn add_note(
210 &self,
211 session_id: &str,
212 content: &str,
213 title: Option<&str>,
214 ) -> Result<String, String> {
215 AppService::add_note(self, session_id, content, title).await
216 }
217
218 async fn log_view(
219 &self,
220 session_id: Option<&str>,
221 limit: Option<usize>,
222 max_chars: Option<usize>,
223 ) -> Result<String, String> {
224 AppService::log_view(self, session_id, limit, max_chars).await
225 }
226
227 async fn stats(
228 &self,
229 strategy_filter: Option<&str>,
230 days: Option<u64>,
231 ) -> Result<String, String> {
232 AppService::stats(self, strategy_filter, days)
233 }
234
235 async fn init(&self, project_root: Option<String>) -> Result<String, String> {
238 AppService::init(self, project_root).await
239 }
240
241 async fn update(&self, project_root: Option<String>) -> Result<String, String> {
242 AppService::update(self, project_root).await
243 }
244
245 async fn migrate(&self, project_root: Option<String>) -> Result<String, String> {
246 AppService::migrate(self, project_root).await
247 }
248
249 async fn card_list(&self, pkg: Option<String>) -> Result<String, String> {
252 AppService::card_list(self, pkg.as_deref())
253 }
254
255 async fn card_get(&self, card_id: &str) -> Result<String, String> {
256 AppService::card_get(self, card_id)
257 }
258
259 async fn card_find(
260 &self,
261 pkg: Option<String>,
262 where_: Option<serde_json::Value>,
263 order_by: Option<serde_json::Value>,
264 limit: Option<usize>,
265 offset: Option<usize>,
266 ) -> Result<String, String> {
267 AppService::card_find(self, pkg, where_, order_by, limit, offset)
268 }
269
270 async fn card_alias_list(&self, pkg: Option<String>) -> Result<String, String> {
271 AppService::card_alias_list(self, pkg.as_deref())
272 }
273
274 async fn card_get_by_alias(&self, name: &str) -> Result<String, String> {
275 AppService::card_get_by_alias(self, name)
276 }
277
278 async fn card_alias_set(
279 &self,
280 name: &str,
281 card_id: &str,
282 pkg: Option<String>,
283 note: Option<String>,
284 ) -> Result<String, String> {
285 AppService::card_alias_set(self, name, card_id, pkg.as_deref(), note.as_deref())
286 }
287
288 async fn card_append(
289 &self,
290 card_id: &str,
291 fields: serde_json::Value,
292 ) -> Result<String, String> {
293 AppService::card_append(self, card_id, fields)
294 }
295
296 async fn card_install(&self, url: String) -> Result<String, String> {
297 AppService::card_install(self, url).await
298 }
299
300 async fn card_samples(
301 &self,
302 card_id: &str,
303 offset: Option<usize>,
304 limit: Option<usize>,
305 where_: Option<serde_json::Value>,
306 ) -> Result<String, String> {
307 AppService::card_samples(self, card_id, offset.unwrap_or(0), limit, where_)
308 }
309
310 async fn card_lineage(
311 &self,
312 card_id: &str,
313 direction: Option<String>,
314 depth: Option<usize>,
315 include_stats: Option<bool>,
316 relation_filter: Option<Vec<String>>,
317 ) -> Result<String, String> {
318 AppService::card_lineage(
319 self,
320 card_id,
321 direction.as_deref(),
322 depth,
323 include_stats,
324 relation_filter,
325 )
326 }
327
328 async fn card_sink_backfill(&self, sink: String, dry_run: bool) -> Result<String, String> {
329 AppService::card_sink_backfill(self, super::card::SinkBackfillParams { sink, dry_run })
330 }
331
332 async fn hub_reindex(
335 &self,
336 output_path: Option<String>,
337 source_dir: Option<String>,
338 ) -> Result<String, String> {
339 let svc = self.clone();
340 tokio::task::spawn_blocking(move || {
341 AppService::hub_reindex(&svc, output_path.as_deref(), source_dir.as_deref())
342 })
343 .await
344 .map_err(|e| format!("hub_reindex task panicked: {e}"))?
345 }
346
347 async fn hub_gendoc(
348 &self,
349 source_dir: String,
350 out_dir: Option<String>,
351 projections: Option<Vec<String>>,
352 config_path: Option<String>,
353 lint_strict: Option<bool>,
354 ) -> Result<String, String> {
355 let svc = self.clone();
356 tokio::task::spawn_blocking(move || {
357 crate::AppService::hub_gendoc(
358 &svc,
359 &source_dir,
360 out_dir.as_deref(),
361 projections.as_deref(),
362 config_path.as_deref(),
363 lint_strict,
364 )
365 })
366 .await
367 .map_err(|e| format!("hub_gendoc task panicked: {e}"))?
368 }
369
370 async fn hub_dist(
371 &self,
372 source_dir: String,
373 output_path: Option<String>,
374 out_dir: Option<String>,
375 preset: Option<String>,
376 project_root: Option<String>,
377 projections: Option<Vec<String>>,
378 config_path: Option<String>,
379 lint_strict: Option<bool>,
380 ) -> Result<String, String> {
381 let svc = self.clone();
382 tokio::task::spawn_blocking(move || {
383 AppService::hub_dist(
384 &svc,
385 &source_dir,
386 output_path.as_deref(),
387 out_dir.as_deref(),
388 preset.as_deref(),
389 project_root.as_deref(),
390 projections.as_deref(),
391 config_path.as_deref(),
392 lint_strict,
393 )
394 })
395 .await
396 .map_err(|e| format!("hub_dist task panicked: {e}"))?
397 }
398
399 async fn hub_info(&self, pkg: String) -> Result<String, String> {
400 let svc = self.clone();
401 tokio::task::spawn_blocking(move || AppService::hub_info(&svc, &pkg))
402 .await
403 .map_err(|e| format!("hub_info task panicked: {e}"))?
404 }
405
406 #[allow(clippy::too_many_arguments)]
407 async fn hub_search(
408 &self,
409 query: Option<String>,
410 category: Option<String>,
411 installed_only: Option<bool>,
412 limit: Option<i32>,
413 sort: Option<String>,
414 filter: Option<serde_json::Value>,
415 fields: Option<Vec<String>>,
416 verbose: Option<String>,
417 ) -> Result<String, String> {
418 let svc = self.clone();
419
420 let filter_map = match filter {
428 None => None,
429 Some(v) => match serde_json::from_value::<HashMap<String, serde_json::Value>>(v) {
430 Ok(map) => Some(map),
431 Err(e) => {
432 tracing::warn!(error = %e, "hub_search: filter value is not a JSON object — treating as no filter");
433 None
434 }
435 },
436 };
437
438 let opts = ListOpts {
443 limit: limit.map(|n| n.max(0) as usize),
444 sort,
445 filter: filter_map,
446 fields,
447 verbose,
448 };
449
450 tokio::task::spawn_blocking(move || {
451 AppService::hub_search(
452 &svc,
453 query.as_deref(),
454 category.as_deref(),
455 installed_only,
456 opts,
457 )
458 })
459 .await
460 .map_err(|e| format!("hub_search task panicked: {e}"))?
461 }
462
463 async fn pkg_read_init_lua(&self, name: &str) -> Result<String, String> {
466 AppService::pkg_read_init_lua(self, name, None)
467 }
468
469 async fn pkg_meta(&self, name: &str) -> Result<String, String> {
470 let filter = serde_json::json!({ "name": name });
471 let json_str = EngineApi::pkg_list(
472 self,
473 None,
474 None,
475 None,
476 Some(filter),
477 None,
478 Some("full".to_string()),
479 )
480 .await?;
481 let val: serde_json::Value = serde_json::from_str(&json_str)
482 .map_err(|e| format!("pkg_meta: failed to parse pkg_list response: {e}"))?;
483 let pkgs = val
484 .get("packages")
485 .and_then(|p| p.as_array())
486 .ok_or_else(|| "pkg_meta: pkg_list response missing 'packages' field".to_string())?;
487 if pkgs.is_empty() {
488 return Err(format!("pkg not found: {name}"));
489 }
490 serde_json::to_string(&pkgs[0]).map_err(|e| format!("pkg_meta: serialize entry: {e}"))
491 }
492
493 async fn pkg_scaffold(
496 &self,
497 name: String,
498 target_dir: Option<String>,
499 category: Option<String>,
500 description: Option<String>,
501 ) -> Result<String, String> {
502 let svc = self.clone();
503 tokio::task::spawn_blocking(move || {
504 AppService::pkg_scaffold(
505 &svc,
506 &name,
507 target_dir.as_deref(),
508 category.as_deref(),
509 description.as_deref(),
510 )
511 })
512 .await
513 .map_err(|e| format!("pkg_scaffold task panicked: {e}"))?
514 }
515
516 async fn info(&self) -> String {
519 AppService::info(self)
520 }
521}