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