Skip to main content

algocline_app/service/
run.rs

1use algocline_core::QueryId;
2use algocline_engine::{FeedResult, VariantPkg};
3
4use super::resolve::{is_package_installed, make_require_code, resolve_code, QueryResponse};
5use super::transcript::write_transcript_log;
6use super::AppService;
7
8impl AppService {
9    /// Execute Lua code with optional JSON context.
10    ///
11    /// `project_root` — optional absolute path to the project root containing
12    /// `alc.lock`. Falls back to `ALC_PROJECT_ROOT` env or ancestor walk.
13    pub async fn run(
14        &self,
15        code: Option<String>,
16        code_file: Option<String>,
17        ctx: Option<serde_json::Value>,
18        project_root: Option<String>,
19    ) -> Result<String, String> {
20        let code = resolve_code(code, code_file)?;
21        let ctx = ctx.unwrap_or(serde_json::Value::Null);
22        let extra = self.resolve_extra_lib_paths(project_root.as_deref());
23        let variants = self.resolve_variant_pkgs(project_root.as_deref());
24        self.start_and_tick(code, ctx, None, extra, variants).await
25    }
26
27    /// Apply a built-in strategy to a task.
28    ///
29    /// If the requested package is not installed, automatically installs the
30    /// bundled package collection from GitHub before executing.
31    ///
32    /// `project_root` — optional absolute path to the project root containing
33    /// `alc.lock`. Falls back to `ALC_PROJECT_ROOT` env or ancestor walk.
34    pub async fn advice(
35        &self,
36        strategy: &str,
37        task: Option<String>,
38        opts: Option<serde_json::Value>,
39        project_root: Option<String>,
40    ) -> Result<String, String> {
41        // Auto-install bundled packages if the requested strategy is missing
42        if !is_package_installed(strategy) {
43            self.auto_install_bundled_packages().await?;
44            if !is_package_installed(strategy) {
45                return Err(format!(
46                    "Package '{strategy}' not found after installing bundled collection. \
47                     Use alc_pkg_install to install it manually."
48                ));
49            }
50        }
51
52        let code = make_require_code(strategy);
53
54        let mut ctx_map = match opts {
55            Some(serde_json::Value::Object(m)) => m,
56            _ => serde_json::Map::new(),
57        };
58        if let Some(task) = task {
59            ctx_map.insert("task".into(), serde_json::Value::String(task));
60        }
61        let ctx = serde_json::Value::Object(ctx_map);
62
63        let extra = self.resolve_extra_lib_paths(project_root.as_deref());
64        let variants = self.resolve_variant_pkgs(project_root.as_deref());
65        self.start_and_tick(code, ctx, Some(strategy), extra, variants)
66            .await
67    }
68
69    /// Continue a paused execution — batch feed.
70    pub async fn continue_batch(
71        &self,
72        session_id: &str,
73        responses: Vec<QueryResponse>,
74    ) -> Result<String, String> {
75        let mut last_result = None;
76        for qr in responses {
77            let qid = QueryId::parse(&qr.query_id);
78            let result = self
79                .registry
80                .feed_response(session_id, &qid, qr.response, qr.usage.as_ref())
81                .await
82                .map_err(|e| format!("Continue failed: {e}"))?;
83            last_result = Some(result);
84        }
85        let result = last_result.ok_or("Empty responses array")?;
86        self.maybe_log_transcript(&result, session_id);
87        let json = result.to_json(session_id).to_string();
88        self.maybe_save_eval(&result, session_id, &json);
89        Ok(json)
90    }
91
92    /// Continue a paused execution — single response (with optional query_id).
93    pub async fn continue_single(
94        &self,
95        session_id: &str,
96        response: String,
97        query_id: Option<&str>,
98        usage: Option<algocline_core::TokenUsage>,
99    ) -> Result<String, String> {
100        let query_id = match query_id {
101            Some(qid) => QueryId::parse(qid),
102            None => self
103                .registry
104                .resolve_sole_pending_id(session_id)
105                .await
106                .map_err(|e| format!("Continue failed: {e}"))?,
107        };
108
109        let result = self
110            .registry
111            .feed_response(session_id, &query_id, response, usage.as_ref())
112            .await
113            .map_err(|e| format!("Continue failed: {e}"))?;
114
115        self.maybe_log_transcript(&result, session_id);
116        let json = result.to_json(session_id).to_string();
117        self.maybe_save_eval(&result, session_id, &json);
118        Ok(json)
119    }
120
121    // ─── Internal ───────────────────────────────────────────────
122
123    pub(super) fn maybe_log_transcript(&self, result: &FeedResult, session_id: &str) {
124        if let FeedResult::Finished(exec_result) = result {
125            let strategy = self
126                .session_strategies
127                .lock()
128                .ok()
129                .and_then(|mut map| map.remove(session_id));
130            write_transcript_log(
131                &self.log_config,
132                session_id,
133                &exec_result.metrics,
134                strategy.as_deref(),
135            );
136        }
137    }
138
139    pub(super) fn maybe_save_eval(&self, result: &FeedResult, session_id: &str, result_json: &str) {
140        if !matches!(result, FeedResult::Finished(_)) {
141            return;
142        }
143        let info = {
144            let mut map = match self.eval_sessions.lock() {
145                Ok(m) => m,
146                Err(_) => return,
147            };
148            map.remove(session_id)
149        };
150        if let Some(strategy) = info {
151            super::eval_store::save_eval_result(&strategy, result_json);
152        }
153    }
154
155    pub(super) async fn start_and_tick(
156        &self,
157        code: String,
158        ctx: serde_json::Value,
159        strategy: Option<&str>,
160        extra_lib_paths: Vec<std::path::PathBuf>,
161        variant_pkgs: Vec<VariantPkg>,
162    ) -> Result<String, String> {
163        let session = self
164            .executor
165            .start_session(code, ctx, extra_lib_paths, variant_pkgs)
166            .await?;
167        let (session_id, result) = self
168            .registry
169            .start_execution(session)
170            .await
171            .map_err(|e| format!("Execution failed: {e}"))?;
172        if let Some(s) = strategy {
173            if let Ok(mut map) = self.session_strategies.lock() {
174                map.insert(session_id.clone(), s.to_string());
175            }
176        }
177        self.maybe_log_transcript(&result, &session_id);
178        Ok(result.to_json(&session_id).to_string())
179    }
180}