Skip to main content

algocline_app/service/
run.rs

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