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: Option<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        if let Some(task) = task {
49            ctx_map.insert("task".into(), serde_json::Value::String(task));
50        }
51        let ctx = serde_json::Value::Object(ctx_map);
52
53        self.start_and_tick(code, ctx, Some(strategy)).await
54    }
55
56    /// Continue a paused execution — batch feed.
57    pub async fn continue_batch(
58        &self,
59        session_id: &str,
60        responses: Vec<QueryResponse>,
61    ) -> Result<String, String> {
62        let mut last_result = None;
63        for qr in responses {
64            let qid = QueryId::parse(&qr.query_id);
65            let result = self
66                .registry
67                .feed_response(session_id, &qid, qr.response, qr.usage.as_ref())
68                .await
69                .map_err(|e| format!("Continue failed: {e}"))?;
70            last_result = Some(result);
71        }
72        let result = last_result.ok_or("Empty responses array")?;
73        self.maybe_log_transcript(&result, session_id);
74        let json = result.to_json(session_id).to_string();
75        self.maybe_save_eval(&result, session_id, &json);
76        Ok(json)
77    }
78
79    /// Continue a paused execution — single response (with optional query_id).
80    pub async fn continue_single(
81        &self,
82        session_id: &str,
83        response: String,
84        query_id: Option<&str>,
85        usage: Option<algocline_core::TokenUsage>,
86    ) -> Result<String, String> {
87        let query_id = match query_id {
88            Some(qid) => QueryId::parse(qid),
89            None => self
90                .registry
91                .resolve_sole_pending_id(session_id)
92                .await
93                .map_err(|e| format!("Continue failed: {e}"))?,
94        };
95
96        let result = self
97            .registry
98            .feed_response(session_id, &query_id, response, usage.as_ref())
99            .await
100            .map_err(|e| format!("Continue failed: {e}"))?;
101
102        self.maybe_log_transcript(&result, session_id);
103        let json = result.to_json(session_id).to_string();
104        self.maybe_save_eval(&result, session_id, &json);
105        Ok(json)
106    }
107
108    // ─── Internal ───────────────────────────────────────────────
109
110    pub(super) fn maybe_log_transcript(&self, result: &FeedResult, session_id: &str) {
111        if let FeedResult::Finished(exec_result) = result {
112            let strategy = self
113                .session_strategies
114                .lock()
115                .ok()
116                .and_then(|mut map| map.remove(session_id));
117            write_transcript_log(
118                &self.log_config,
119                session_id,
120                &exec_result.metrics,
121                strategy.as_deref(),
122            );
123        }
124    }
125
126    pub(super) fn maybe_save_eval(&self, result: &FeedResult, session_id: &str, result_json: &str) {
127        if !matches!(result, FeedResult::Finished(_)) {
128            return;
129        }
130        let strategy = {
131            let mut map = match self.eval_sessions.lock() {
132                Ok(m) => m,
133                Err(_) => return,
134            };
135            map.remove(session_id)
136        };
137        if let Some(strategy) = strategy {
138            super::eval_store::save_eval_result(&strategy, result_json);
139        }
140    }
141
142    pub(super) async fn start_and_tick(
143        &self,
144        code: String,
145        ctx: serde_json::Value,
146        strategy: Option<&str>,
147    ) -> Result<String, String> {
148        let session = self.executor.start_session(code, ctx).await?;
149        let (session_id, result) = self
150            .registry
151            .start_execution(session)
152            .await
153            .map_err(|e| format!("Execution failed: {e}"))?;
154        if let Some(s) = strategy {
155            if let Ok(mut map) = self.session_strategies.lock() {
156                map.insert(session_id.clone(), s.to_string());
157            }
158        }
159        self.maybe_log_transcript(&result, &session_id);
160        Ok(result.to_json(&session_id).to_string())
161    }
162}