Skip to main content

incurs_codemode/
engine.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::{ConnectorDescription, DispatchResponse, DispatchSession, StepResponse};
8
9/// Result returned by a Code Mode program executor.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ExecuteResult {
12    /// Successful program result.
13    pub result: Option<Value>,
14    /// Program or sandbox error.
15    pub error: Option<String>,
16    /// Captured console output.
17    pub logs: Vec<String>,
18}
19
20/// Host request emitted by an executing Code Mode program.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22#[serde(tag = "kind", rename_all = "snake_case")]
23pub enum DispatchRequest {
24    /// Execute or replay one connector call.
25    Call {
26        /// Durable execution identifier.
27        execution_id: String,
28        /// Program-assigned deterministic sequence.
29        seq: u64,
30        /// Connector namespace.
31        connector: String,
32        /// Connector method.
33        method: String,
34        /// JSON-safe tagged arguments.
35        arguments: Value,
36    },
37    /// Decide whether a local `codemode.step` callback should run.
38    BeginStep {
39        /// Durable execution identifier.
40        execution_id: String,
41        /// Program-assigned deterministic sequence.
42        seq: u64,
43        /// Stable step name.
44        name: String,
45    },
46    /// Record a completed local `codemode.step` callback.
47    RecordStep {
48        /// Durable execution identifier.
49        execution_id: String,
50        /// Program-assigned deterministic sequence.
51        seq: u64,
52        /// JSON-safe tagged callback result.
53        result: Value,
54    },
55}
56
57/// Executes model-generated JavaScript for one replay pass.
58///
59/// Executors provide isolation and host bridging. The durable lifecycle,
60/// connector policy, approvals, replay, and rollback remain in [`crate::CodeMode`].
61#[async_trait(?Send)]
62pub trait CodeExecutor {
63    /// Executes one pass and returns its result or captured program error.
64    async fn execute(
65        &self,
66        code: &str,
67        connectors: &[ConnectorDescription],
68        execution_id: &str,
69        host: Arc<ExecutionHost>,
70    ) -> Result<ExecuteResult, String>;
71}
72
73/// Supplies monotonic-enough Unix millisecond timestamps to Code Mode.
74pub trait Clock: Send + Sync {
75    /// Returns the current Unix timestamp in milliseconds.
76    fn now_ms(&self) -> u64;
77}
78
79/// Native system clock used by default.
80#[derive(Debug, Clone, Copy, Default)]
81pub struct SystemClock;
82
83impl Clock for SystemClock {
84    fn now_ms(&self) -> u64 {
85        std::time::SystemTime::now()
86            .duration_since(std::time::UNIX_EPOCH)
87            .unwrap_or_default()
88            .as_millis() as u64
89    }
90}
91
92/// Execution-scoped bridge from a local engine into durable dispatch.
93pub struct ExecutionHost {
94    session: Arc<DispatchSession>,
95    clock: Arc<dyn Clock>,
96}
97
98impl ExecutionHost {
99    pub(crate) fn new(session: Arc<DispatchSession>, clock: Arc<dyn Clock>) -> Self {
100        Self { session, clock }
101    }
102
103    /// Executes or replays one connector call.
104    pub async fn call(
105        &self,
106        seq: u64,
107        connector: &str,
108        method: &str,
109        arguments: Value,
110    ) -> DispatchResponse {
111        self.session
112            .call_at(seq, connector, method, arguments, self.clock.now_ms())
113            .await
114    }
115
116    /// Begins a durable local callback step.
117    pub async fn begin_step(&self, seq: u64, name: &str) -> Result<StepResponse, String> {
118        self.session
119            .begin_step_at(seq, name, self.clock.now_ms())
120            .await
121            .map_err(|error| error.to_string())
122    }
123
124    /// Records a completed durable local callback step.
125    pub async fn record_step(&self, seq: u64, result: Value) -> Result<(), String> {
126        self.session
127            .record_step(seq, result, self.clock.now_ms())
128            .await
129            .map_err(|error| error.to_string())
130    }
131}