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 ) -> Result<String, String> {
184 AppService::pkg_remove(self, name, project_root, version).await
185 }
186
187 async fn pkg_repair(
188 &self,
189 name: Option<String>,
190 project_root: Option<String>,
191 ) -> Result<String, String> {
192 AppService::pkg_repair(self, name, project_root).await
193 }
194
195 async fn add_note(
198 &self,
199 session_id: &str,
200 content: &str,
201 title: Option<&str>,
202 ) -> Result<String, String> {
203 AppService::add_note(self, session_id, content, title).await
204 }
205
206 async fn log_view(
207 &self,
208 session_id: Option<&str>,
209 limit: Option<usize>,
210 max_chars: Option<usize>,
211 ) -> Result<String, String> {
212 AppService::log_view(self, session_id, limit, max_chars).await
213 }
214
215 async fn stats(
216 &self,
217 strategy_filter: Option<&str>,
218 days: Option<u64>,
219 ) -> Result<String, String> {
220 AppService::stats(self, strategy_filter, days)
221 }
222
223 async fn init(&self, project_root: Option<String>) -> Result<String, String> {
226 AppService::init(self, project_root).await
227 }
228
229 async fn update(&self, project_root: Option<String>) -> Result<String, String> {
230 AppService::update(self, project_root).await
231 }
232
233 async fn migrate(&self, project_root: Option<String>) -> Result<String, String> {
234 AppService::migrate(self, project_root).await
235 }
236
237 async fn card_list(&self, pkg: Option<String>) -> Result<String, String> {
240 AppService::card_list(self, pkg.as_deref())
241 }
242
243 async fn card_get(&self, card_id: &str) -> Result<String, String> {
244 AppService::card_get(self, card_id)
245 }
246
247 async fn card_find(
248 &self,
249 pkg: Option<String>,
250 where_: Option<serde_json::Value>,
251 order_by: Option<serde_json::Value>,
252 limit: Option<usize>,
253 offset: Option<usize>,
254 ) -> Result<String, String> {
255 AppService::card_find(self, pkg, where_, order_by, limit, offset)
256 }
257
258 async fn card_alias_list(&self, pkg: Option<String>) -> Result<String, String> {
259 AppService::card_alias_list(self, pkg.as_deref())
260 }
261
262 async fn card_get_by_alias(&self, name: &str) -> Result<String, String> {
263 AppService::card_get_by_alias(self, name)
264 }
265
266 async fn card_alias_set(
267 &self,
268 name: &str,
269 card_id: &str,
270 pkg: Option<String>,
271 note: Option<String>,
272 ) -> Result<String, String> {
273 AppService::card_alias_set(self, name, card_id, pkg.as_deref(), note.as_deref())
274 }
275
276 async fn card_append(
277 &self,
278 card_id: &str,
279 fields: serde_json::Value,
280 ) -> Result<String, String> {
281 AppService::card_append(self, card_id, fields)
282 }
283
284 async fn card_install(&self, url: String) -> Result<String, String> {
285 AppService::card_install(self, url).await
286 }
287
288 async fn card_samples(
289 &self,
290 card_id: &str,
291 offset: Option<usize>,
292 limit: Option<usize>,
293 where_: Option<serde_json::Value>,
294 ) -> Result<String, String> {
295 AppService::card_samples(self, card_id, offset.unwrap_or(0), limit, where_)
296 }
297
298 async fn card_lineage(
299 &self,
300 card_id: &str,
301 direction: Option<String>,
302 depth: Option<usize>,
303 include_stats: Option<bool>,
304 relation_filter: Option<Vec<String>>,
305 ) -> Result<String, String> {
306 AppService::card_lineage(
307 self,
308 card_id,
309 direction.as_deref(),
310 depth,
311 include_stats,
312 relation_filter,
313 )
314 }
315
316 async fn card_sink_backfill(&self, sink: String, dry_run: bool) -> Result<String, String> {
317 AppService::card_sink_backfill(self, super::card::SinkBackfillParams { sink, dry_run })
318 }
319
320 async fn hub_reindex(
323 &self,
324 output_path: Option<String>,
325 source_dir: Option<String>,
326 ) -> Result<String, String> {
327 let svc = self.clone();
328 tokio::task::spawn_blocking(move || {
329 AppService::hub_reindex(&svc, output_path.as_deref(), source_dir.as_deref())
330 })
331 .await
332 .map_err(|e| format!("hub_reindex task panicked: {e}"))?
333 }
334
335 async fn hub_info(&self, pkg: String) -> Result<String, String> {
336 let svc = self.clone();
337 tokio::task::spawn_blocking(move || AppService::hub_info(&svc, &pkg))
338 .await
339 .map_err(|e| format!("hub_info task panicked: {e}"))?
340 }
341
342 #[allow(clippy::too_many_arguments)]
343 async fn hub_search(
344 &self,
345 query: Option<String>,
346 category: Option<String>,
347 installed_only: Option<bool>,
348 limit: Option<i32>,
349 sort: Option<String>,
350 filter: Option<serde_json::Value>,
351 fields: Option<Vec<String>>,
352 verbose: Option<String>,
353 ) -> Result<String, String> {
354 let svc = self.clone();
355
356 let filter_map = match filter {
364 None => None,
365 Some(v) => match serde_json::from_value::<HashMap<String, serde_json::Value>>(v) {
366 Ok(map) => Some(map),
367 Err(e) => {
368 tracing::warn!(error = %e, "hub_search: filter value is not a JSON object — treating as no filter");
369 None
370 }
371 },
372 };
373
374 let opts = ListOpts {
379 limit: limit.map(|n| n.max(0) as usize),
380 sort,
381 filter: filter_map,
382 fields,
383 verbose,
384 };
385
386 tokio::task::spawn_blocking(move || {
387 AppService::hub_search(
388 &svc,
389 query.as_deref(),
390 category.as_deref(),
391 installed_only,
392 opts,
393 )
394 })
395 .await
396 .map_err(|e| format!("hub_search task panicked: {e}"))?
397 }
398
399 async fn info(&self) -> String {
402 AppService::info(self)
403 }
404}