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