Skip to main content

algocline_core/
engine_api.rs

1use async_trait::async_trait;
2
3// ─── Parameter types (transport-independent) ─────────────────────
4
5/// A single query response in a batch feed.
6#[derive(Debug)]
7pub struct QueryResponse {
8    /// Query ID (e.g. "q-0", "q-1").
9    pub query_id: String,
10    /// The host LLM's response for this query.
11    pub response: String,
12    /// Token usage reported by the host for this query.
13    pub usage: Option<crate::TokenUsage>,
14}
15
16// ─── Engine API trait ────────────────────────────────────────────
17
18/// Transport-independent API for the algocline engine.
19///
20/// Abstracts the full public surface of AppService so that callers
21/// (MCP handler, future daemon client, etc.) can operate through
22/// `Arc<dyn EngineApi>` without depending on the concrete implementation.
23///
24/// All methods are async to support both local (in-process) and remote
25/// (socket/HTTP) implementations uniformly.
26#[async_trait]
27pub trait EngineApi: Send + Sync {
28    // ─── Core execution ──────────────────────────────────────
29
30    /// Execute Lua code with optional JSON context.
31    async fn run(
32        &self,
33        code: Option<String>,
34        code_file: Option<String>,
35        ctx: Option<serde_json::Value>,
36        project_root: Option<String>,
37    ) -> Result<String, String>;
38
39    /// Apply an installed strategy package. Task is optional.
40    async fn advice(
41        &self,
42        strategy: &str,
43        task: Option<String>,
44        opts: Option<serde_json::Value>,
45        project_root: Option<String>,
46    ) -> Result<String, String>;
47
48    /// Continue a paused execution — single response (with optional query_id).
49    async fn continue_single(
50        &self,
51        session_id: &str,
52        response: String,
53        query_id: Option<&str>,
54        usage: Option<crate::TokenUsage>,
55    ) -> Result<String, String>;
56
57    /// Continue a paused execution — batch feed.
58    async fn continue_batch(
59        &self,
60        session_id: &str,
61        responses: Vec<QueryResponse>,
62    ) -> Result<String, String>;
63
64    // ─── Session status ──────────────────────────────────────
65
66    /// Query active session status.
67    async fn status(&self, session_id: Option<&str>) -> Result<String, String>;
68
69    // ─── Evaluation ──────────────────────────────────────────
70
71    /// Run an evalframe evaluation suite.
72    ///
73    /// `auto_card`: when true, emit an immutable Card
74    /// (`~/.algocline/cards/{strategy}/{card_id}.toml`) summarizing the run.
75    async fn eval(
76        &self,
77        scenario: Option<String>,
78        scenario_file: Option<String>,
79        scenario_name: Option<String>,
80        strategy: &str,
81        strategy_opts: Option<serde_json::Value>,
82        auto_card: bool,
83    ) -> Result<String, String>;
84
85    /// List eval history, optionally filtered by strategy.
86    async fn eval_history(&self, strategy: Option<&str>, limit: usize) -> Result<String, String>;
87
88    /// View a specific eval result by ID.
89    async fn eval_detail(&self, eval_id: &str) -> Result<String, String>;
90
91    /// Compare two eval results with statistical significance testing.
92    async fn eval_compare(&self, eval_id_a: &str, eval_id_b: &str) -> Result<String, String>;
93
94    // ─── Scenarios ───────────────────────────────────────────
95
96    /// List available scenarios.
97    async fn scenario_list(&self) -> Result<String, String>;
98
99    /// Show the content of a named scenario.
100    async fn scenario_show(&self, name: &str) -> Result<String, String>;
101
102    /// Install scenarios from a Git URL or local path.
103    async fn scenario_install(&self, url: String) -> Result<String, String>;
104
105    // ─── Packages ────────────────────────────────────────────
106
107    /// Link a local directory as a project-local package (symlink to cache).
108    ///
109    /// Scope selection:
110    /// - `scope = None` or `Some("global")` — symlink into `~/.algocline/packages/`
111    ///   (visible to all projects).
112    /// - `scope = Some("variant")` — record the path in `alc.local.toml`
113    ///   at the project root (worktree-scoped override, git-ignored). No
114    ///   symlink is created.
115    /// - Any other value → `Err("invalid scope: ...")`.
116    ///
117    /// `project_root` is only consulted when `scope = Some("variant")`.
118    /// If `None`, falls back to `ALC_PROJECT_ROOT` env or ancestor walk
119    /// from cwd.
120    async fn pkg_link(
121        &self,
122        path: String,
123        name: Option<String>,
124        force: Option<bool>,
125        scope: Option<String>,
126        project_root: Option<String>,
127    ) -> Result<String, String>;
128
129    /// List installed packages with metadata.
130    ///
131    /// When `project_root` is provided, project-local packages from `alc.toml`/`alc.lock`
132    /// are included with `scope: "project"`. Global packages carry `scope: "global"`.
133    async fn pkg_list(&self, project_root: Option<String>) -> Result<String, String>;
134
135    /// Install a package from a Git URL or local path.
136    async fn pkg_install(&self, url: String, name: Option<String>) -> Result<String, String>;
137
138    /// Remove a symlinked package from `~/.algocline/packages/`.
139    ///
140    /// Only removes symlinks; for installed (copied) packages, use `pkg_remove`.
141    async fn pkg_unlink(&self, name: String) -> Result<String, String>;
142
143    /// Remove a package declaration from `alc.toml` and `alc.lock`.
144    ///
145    /// Requires an `alc.toml` to be found (via `project_root` or ancestor walk).
146    /// Does NOT delete physical files from `~/.algocline/packages/`.
147    async fn pkg_remove(
148        &self,
149        name: &str,
150        project_root: Option<String>,
151        version: Option<String>,
152    ) -> Result<String, String>;
153
154    /// Heal broken package state by reinstalling entries whose installed
155    /// directory is missing. Other broken kinds (dangling symlink,
156    /// declared-path missing) are surfaced as `unrepairable` with a
157    /// suggested remediation.
158    async fn pkg_repair(
159        &self,
160        name: Option<String>,
161        project_root: Option<String>,
162    ) -> Result<String, String>;
163
164    // ─── Logging ─────────────────────────────────────────────
165
166    /// Append a note to a session's log file.
167    async fn add_note(
168        &self,
169        session_id: &str,
170        content: &str,
171        title: Option<&str>,
172    ) -> Result<String, String>;
173
174    /// View session logs.
175    async fn log_view(
176        &self,
177        session_id: Option<&str>,
178        limit: Option<usize>,
179        max_chars: Option<usize>,
180    ) -> Result<String, String>;
181
182    /// Aggregate stats across all logged sessions.
183    async fn stats(
184        &self,
185        strategy_filter: Option<&str>,
186        days: Option<u64>,
187    ) -> Result<String, String>;
188
189    // ─── Project lifecycle ────────────────────────────────────
190
191    /// Initialize `alc.toml` in the given project root.
192    ///
193    /// Creates a minimal `alc.toml` (`[packages]` section only).
194    /// Fails if `alc.toml` already exists (no overwrite).
195    async fn init(&self, project_root: Option<String>) -> Result<String, String>;
196
197    /// Re-resolve all `alc.toml` entries and rewrite `alc.lock`.
198    ///
199    /// Requires an `alc.toml` to be present. Returns resolved count and errors.
200    async fn update(&self, project_root: Option<String>) -> Result<String, String>;
201
202    /// Migrate a legacy `alc.lock` to `alc.toml` + new `alc.lock` format.
203    ///
204    /// Detects legacy format via `linked_at` / `local_dir` fields.
205    /// Backs up the old lock file as `alc.lock.bak`.
206    async fn migrate(&self, project_root: Option<String>) -> Result<String, String>;
207
208    // ─── Cards ───────────────────────────────────────────────
209
210    /// List Card summaries, optionally filtered by pkg.
211    async fn card_list(&self, pkg: Option<String>) -> Result<String, String>;
212
213    /// Fetch a full Card by id.
214    async fn card_get(&self, card_id: &str) -> Result<String, String>;
215
216    /// Filter/sort Cards using the Prisma-style `where` DSL.
217    ///
218    /// - `pkg`: restricts filesystem scan to a single pkg subdir (I/O hint).
219    /// - `where_`: nested-object predicate (see `card::parse_where`).
220    /// - `order_by`: array of dotted-path sort keys; `-` prefix = desc.
221    /// - `limit` / `offset`: pagination.
222    async fn card_find(
223        &self,
224        pkg: Option<String>,
225        where_: Option<serde_json::Value>,
226        order_by: Option<serde_json::Value>,
227        limit: Option<usize>,
228        offset: Option<usize>,
229    ) -> Result<String, String>;
230
231    /// List aliases, optionally filtered by pkg.
232    async fn card_alias_list(&self, pkg: Option<String>) -> Result<String, String>;
233
234    /// Resolve an alias name to its bound Card and return the full Card JSON.
235    async fn card_get_by_alias(&self, name: &str) -> Result<String, String>;
236
237    /// Bind (or rebind) an alias to a Card.
238    async fn card_alias_set(
239        &self,
240        name: &str,
241        card_id: &str,
242        pkg: Option<String>,
243        note: Option<String>,
244    ) -> Result<String, String>;
245
246    /// Append new top-level fields to an existing Card (additive-only).
247    async fn card_append(&self, card_id: &str, fields: serde_json::Value)
248        -> Result<String, String>;
249
250    /// Install Cards from a Card Collection repo (Git URL or local path).
251    async fn card_install(&self, url: String) -> Result<String, String>;
252
253    /// Read per-case samples from a Card's sidecar JSONL file.
254    ///
255    /// `where_` applies the same Prisma-style DSL used by `card_find`
256    /// to each sample row; offset/limit page the post-filter stream.
257    async fn card_samples(
258        &self,
259        card_id: &str,
260        offset: Option<usize>,
261        limit: Option<usize>,
262        where_: Option<serde_json::Value>,
263    ) -> Result<String, String>;
264
265    /// Walk a Card's lineage tree via `metadata.prior_card_id`.
266    ///
267    /// - `direction`: `"up"` | `"down"` | `"both"` (default `"up"`).
268    /// - `depth`: max traversal depth (default 10).
269    /// - `include_stats`: include each node's `[stats]` section.
270    /// - `relation_filter`: optional list of accepted `prior_relation` values.
271    async fn card_lineage(
272        &self,
273        card_id: &str,
274        direction: Option<String>,
275        depth: Option<usize>,
276        include_stats: Option<bool>,
277        relation_filter: Option<Vec<String>>,
278    ) -> Result<String, String>;
279
280    // ─── Hub ─────────────────────────────────────────────────
281
282    /// Rebuild hub index from a packages directory.
283    ///
284    /// When `source_dir` is provided, scans that directory directly
285    /// (pure metadata, no manifest).  When omitted, scans `~/.algocline/packages/`.
286    async fn hub_reindex(
287        &self,
288        output_path: Option<String>,
289        source_dir: Option<String>,
290    ) -> Result<String, String>;
291
292    /// Show detailed information for a single package.
293    async fn hub_info(&self, pkg: String) -> Result<String, String>;
294
295    /// Search packages across remote index + local install state.
296    async fn hub_search(
297        &self,
298        query: Option<String>,
299        category: Option<String>,
300        installed_only: Option<bool>,
301        limit: Option<usize>,
302    ) -> Result<String, String>;
303
304    // ─── Diagnostics ─────────────────────────────────────────
305
306    /// Show server configuration and diagnostic info.
307    async fn info(&self) -> String;
308}