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).await
172 }
173
174 async fn pkg_install(&self, url: String, name: Option<String>) -> Result<String, String> {
175 AppService::pkg_install(self, url, name).await
176 }
177
178 async fn pkg_remove(
179 &self,
180 name: &str,
181 project_root: Option<String>,
182 version: Option<String>,
183 scope: Option<String>,
184 ) -> Result<String, String> {
185 AppService::pkg_remove(self, name, project_root, version, scope).await
186 }
187
188 async fn pkg_repair(
189 &self,
190 name: Option<String>,
191 project_root: Option<String>,
192 ) -> Result<String, String> {
193 AppService::pkg_repair(self, name, project_root).await
194 }
195
196 async fn pkg_doctor(
197 &self,
198 name: Option<String>,
199 project_root: Option<String>,
200 ) -> Result<String, String> {
201 AppService::pkg_doctor(self, name, project_root).await
202 }
203
204 async fn add_note(
207 &self,
208 session_id: &str,
209 content: &str,
210 title: Option<&str>,
211 ) -> Result<String, String> {
212 AppService::add_note(self, session_id, content, title).await
213 }
214
215 async fn log_view(
216 &self,
217 session_id: Option<&str>,
218 limit: Option<usize>,
219 max_chars: Option<usize>,
220 ) -> Result<String, String> {
221 AppService::log_view(self, session_id, limit, max_chars).await
222 }
223
224 async fn stats(
225 &self,
226 strategy_filter: Option<&str>,
227 days: Option<u64>,
228 ) -> Result<String, String> {
229 AppService::stats(self, strategy_filter, days)
230 }
231
232 async fn init(&self, project_root: Option<String>) -> Result<String, String> {
235 AppService::init(self, project_root).await
236 }
237
238 async fn update(&self, project_root: Option<String>) -> Result<String, String> {
239 AppService::update(self, project_root).await
240 }
241
242 async fn migrate(&self, project_root: Option<String>) -> Result<String, String> {
243 AppService::migrate(self, project_root).await
244 }
245
246 async fn card_list(&self, pkg: Option<String>) -> Result<String, String> {
249 AppService::card_list(self, pkg.as_deref())
250 }
251
252 async fn card_get(&self, card_id: &str) -> Result<String, String> {
253 AppService::card_get(self, card_id)
254 }
255
256 async fn card_find(
257 &self,
258 pkg: Option<String>,
259 where_: Option<serde_json::Value>,
260 order_by: Option<serde_json::Value>,
261 limit: Option<usize>,
262 offset: Option<usize>,
263 ) -> Result<String, String> {
264 AppService::card_find(self, pkg, where_, order_by, limit, offset)
265 }
266
267 async fn card_alias_list(&self, pkg: Option<String>) -> Result<String, String> {
268 AppService::card_alias_list(self, pkg.as_deref())
269 }
270
271 async fn card_get_by_alias(&self, name: &str) -> Result<String, String> {
272 AppService::card_get_by_alias(self, name)
273 }
274
275 async fn card_alias_set(
276 &self,
277 name: &str,
278 card_id: &str,
279 pkg: Option<String>,
280 note: Option<String>,
281 ) -> Result<String, String> {
282 AppService::card_alias_set(self, name, card_id, pkg.as_deref(), note.as_deref())
283 }
284
285 async fn card_append(
286 &self,
287 card_id: &str,
288 fields: serde_json::Value,
289 ) -> Result<String, String> {
290 AppService::card_append(self, card_id, fields)
291 }
292
293 async fn card_install(&self, url: String) -> Result<String, String> {
294 AppService::card_install(self, url).await
295 }
296
297 async fn card_samples(
298 &self,
299 card_id: &str,
300 offset: Option<usize>,
301 limit: Option<usize>,
302 where_: Option<serde_json::Value>,
303 ) -> Result<String, String> {
304 AppService::card_samples(self, card_id, offset.unwrap_or(0), limit, where_)
305 }
306
307 async fn card_lineage(
308 &self,
309 card_id: &str,
310 direction: Option<String>,
311 depth: Option<usize>,
312 include_stats: Option<bool>,
313 relation_filter: Option<Vec<String>>,
314 ) -> Result<String, String> {
315 AppService::card_lineage(
316 self,
317 card_id,
318 direction.as_deref(),
319 depth,
320 include_stats,
321 relation_filter,
322 )
323 }
324
325 async fn card_sink_backfill(&self, sink: String, dry_run: bool) -> Result<String, String> {
326 AppService::card_sink_backfill(self, super::card::SinkBackfillParams { sink, dry_run })
327 }
328
329 async fn hub_reindex(
332 &self,
333 output_path: Option<String>,
334 source_dir: Option<String>,
335 ) -> Result<String, String> {
336 let svc = self.clone();
337 tokio::task::spawn_blocking(move || {
338 AppService::hub_reindex(&svc, output_path.as_deref(), source_dir.as_deref())
339 })
340 .await
341 .map_err(|e| format!("hub_reindex task panicked: {e}"))?
342 }
343
344 async fn hub_gendoc(
345 &self,
346 source_dir: String,
347 out_dir: Option<String>,
348 projections: Option<Vec<String>>,
349 config_path: Option<String>,
350 lint_strict: Option<bool>,
351 ) -> Result<String, String> {
352 let svc = self.clone();
353 tokio::task::spawn_blocking(move || {
354 crate::AppService::hub_gendoc(
355 &svc,
356 &source_dir,
357 out_dir.as_deref(),
358 projections.as_deref(),
359 config_path.as_deref(),
360 lint_strict,
361 )
362 })
363 .await
364 .map_err(|e| format!("hub_gendoc task panicked: {e}"))?
365 }
366
367 async fn hub_dist(
368 &self,
369 source_dir: String,
370 output_path: Option<String>,
371 out_dir: Option<String>,
372 preset: Option<String>,
373 project_root: Option<String>,
374 projections: Option<Vec<String>>,
375 config_path: Option<String>,
376 lint_strict: Option<bool>,
377 ) -> Result<String, String> {
378 let svc = self.clone();
379 tokio::task::spawn_blocking(move || {
380 AppService::hub_dist(
381 &svc,
382 &source_dir,
383 output_path.as_deref(),
384 out_dir.as_deref(),
385 preset.as_deref(),
386 project_root.as_deref(),
387 projections.as_deref(),
388 config_path.as_deref(),
389 lint_strict,
390 )
391 })
392 .await
393 .map_err(|e| format!("hub_dist task panicked: {e}"))?
394 }
395
396 async fn hub_info(&self, pkg: String) -> Result<String, String> {
397 let svc = self.clone();
398 tokio::task::spawn_blocking(move || AppService::hub_info(&svc, &pkg))
399 .await
400 .map_err(|e| format!("hub_info task panicked: {e}"))?
401 }
402
403 #[allow(clippy::too_many_arguments)]
404 async fn hub_search(
405 &self,
406 query: Option<String>,
407 category: Option<String>,
408 installed_only: Option<bool>,
409 limit: Option<i32>,
410 sort: Option<String>,
411 filter: Option<serde_json::Value>,
412 fields: Option<Vec<String>>,
413 verbose: Option<String>,
414 ) -> Result<String, String> {
415 let svc = self.clone();
416
417 let filter_map = match filter {
425 None => None,
426 Some(v) => match serde_json::from_value::<HashMap<String, serde_json::Value>>(v) {
427 Ok(map) => Some(map),
428 Err(e) => {
429 tracing::warn!(error = %e, "hub_search: filter value is not a JSON object — treating as no filter");
430 None
431 }
432 },
433 };
434
435 let opts = ListOpts {
440 limit: limit.map(|n| n.max(0) as usize),
441 sort,
442 filter: filter_map,
443 fields,
444 verbose,
445 };
446
447 tokio::task::spawn_blocking(move || {
448 AppService::hub_search(
449 &svc,
450 query.as_deref(),
451 category.as_deref(),
452 installed_only,
453 opts,
454 )
455 })
456 .await
457 .map_err(|e| format!("hub_search task panicked: {e}"))?
458 }
459
460 async fn pkg_scaffold(
463 &self,
464 name: String,
465 target_dir: Option<String>,
466 category: Option<String>,
467 description: Option<String>,
468 ) -> Result<String, String> {
469 let svc = self.clone();
470 tokio::task::spawn_blocking(move || {
471 AppService::pkg_scaffold(
472 &svc,
473 &name,
474 target_dir.as_deref(),
475 category.as_deref(),
476 description.as_deref(),
477 )
478 })
479 .await
480 .map_err(|e| format!("pkg_scaffold task panicked: {e}"))?
481 }
482
483 async fn info(&self) -> String {
486 AppService::info(self)
487 }
488}