1use std::collections::HashMap;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use incurs::command::RequestContext;
6use incurs::tool::{ToolCallControl, ToolEvent, ToolEventSink};
7use serde_json::Value;
8use tokio::sync::Mutex;
9use tokio_util::sync::CancellationToken;
10
11use crate::{
12 ArtifactStore, CapabilitySnapshot, Clock, CodeExecutor, CodeModeRuntime, Connector,
13 DispatchRequest, DispatchSession, ExecutionEvent, ExecutionHost, ExecutionState,
14 ExecutionStatus, RuntimeStore, SearchOutput, SystemClock, ToolContext,
15};
16
17#[derive(Clone, Default)]
19pub struct CodeModeRunOptions {
20 pub cancellation: CancellationToken,
22 pub request: Option<RequestContext>,
24}
25
26struct RuntimeEventSink {
27 runtime: Arc<CodeModeRuntime>,
28 execution_id: String,
29 clock: Arc<dyn Clock>,
30}
31
32#[async_trait]
33impl ToolEventSink for RuntimeEventSink {
34 async fn emit(&self, event: ToolEvent) {
35 let at = self.clock.now_ms();
36 let event = match event {
37 ToolEvent::Progress { message, fraction } => ExecutionEvent::Progress {
38 message,
39 fraction,
40 at,
41 },
42 ToolEvent::Log { level, message } => ExecutionEvent::Log { level, message, at },
43 ToolEvent::Chunk { data } => ExecutionEvent::Chunk { data, at },
44 };
45 let _ = self.runtime.event(&self.execution_id, event).await;
46 }
47}
48
49pub struct CodeMode {
51 runtime: Arc<CodeModeRuntime>,
52 executor: Box<dyn CodeExecutor>,
53 connectors: Vec<Arc<dyn Connector>>,
54 clock: Arc<dyn Clock>,
55 contexts: Mutex<HashMap<String, ToolContext>>,
56}
57
58impl CodeMode {
59 pub fn new(
61 store: Arc<dyn RuntimeStore>,
62 executor: impl CodeExecutor + 'static,
63 connectors: Vec<Arc<dyn Connector>>,
64 ) -> Self {
65 Self::with_clock(store, executor, connectors, SystemClock)
66 }
67
68 pub fn with_artifact_store(
70 store: Arc<dyn RuntimeStore>,
71 artifacts: Arc<dyn ArtifactStore>,
72 executor: impl CodeExecutor + 'static,
73 connectors: Vec<Arc<dyn Connector>>,
74 ) -> Self {
75 Self {
76 runtime: Arc::new(CodeModeRuntime::with_artifacts(store, artifacts)),
77 executor: Box::new(executor),
78 connectors,
79 clock: Arc::new(SystemClock),
80 contexts: Mutex::new(HashMap::new()),
81 }
82 }
83
84 pub fn with_clock(
86 store: Arc<dyn RuntimeStore>,
87 executor: impl CodeExecutor + 'static,
88 connectors: Vec<Arc<dyn Connector>>,
89 clock: impl Clock + 'static,
90 ) -> Self {
91 Self {
92 runtime: Arc::new(CodeModeRuntime::new(store)),
93 executor: Box::new(executor),
94 connectors,
95 clock: Arc::new(clock),
96 contexts: Mutex::new(HashMap::new()),
97 }
98 }
99
100 pub fn with_clock_and_artifact_store(
102 store: Arc<dyn RuntimeStore>,
103 artifacts: Arc<dyn ArtifactStore>,
104 executor: impl CodeExecutor + 'static,
105 connectors: Vec<Arc<dyn Connector>>,
106 clock: impl Clock + 'static,
107 ) -> Self {
108 Self {
109 runtime: Arc::new(CodeModeRuntime::with_artifacts(store, artifacts)),
110 executor: Box::new(executor),
111 connectors,
112 clock: Arc::new(clock),
113 contexts: Mutex::new(HashMap::new()),
114 }
115 }
116
117 pub fn runtime(&self) -> Arc<CodeModeRuntime> {
119 Arc::clone(&self.runtime)
120 }
121
122 pub async fn instructions(&self) -> Result<String, String> {
124 let mut descriptions = Vec::new();
125 for connector in &self.connectors {
126 descriptions.push(connector.describe().await?);
127 }
128 let mut sections = descriptions
129 .iter()
130 .filter_map(|connector| {
131 connector
132 .instructions
133 .as_ref()
134 .map(|instructions| format!("## {}\n\n{instructions}", connector.name))
135 })
136 .collect::<Vec<_>>();
137 sections.extend(descriptions.iter().map(crate::generate_types));
138 Ok(sections.join("\n\n"))
139 }
140
141 pub async fn search(&self, query: &str) -> Result<SearchOutput, String> {
143 let mut descriptions = Vec::new();
144 for connector in &self.connectors {
145 descriptions.push(connector.describe().await?);
146 }
147 let snippets = self
148 .runtime
149 .snippets()
150 .await
151 .map_err(|error| error.to_string())?;
152 Ok(crate::search(query, &descriptions, &snippets))
153 }
154
155 pub async fn execution(&self, execution_id: &str) -> Result<ExecutionState, String> {
157 self.require(execution_id).await
158 }
159
160 pub async fn execution_snapshot(&self, execution_id: &str) -> Result<ExecutionState, String> {
162 self.runtime
163 .execution_snapshot(execution_id)
164 .await
165 .map_err(|error| error.to_string())?
166 .ok_or_else(|| format!("Execution \"{execution_id}\" not found"))
167 }
168
169 pub async fn artifact(&self, execution_id: &str, artifact_id: &str) -> Result<Value, String> {
171 self.runtime
172 .artifact(execution_id, artifact_id)
173 .await
174 .map_err(|error| error.to_string())?
175 .ok_or_else(|| {
176 format!("Artifact \"{artifact_id}\" not found for execution \"{execution_id}\"")
177 })
178 }
179
180 pub async fn events(&self, execution_id: &str) -> Result<Vec<ExecutionEvent>, String> {
182 Ok(self.require(execution_id).await?.events)
183 }
184
185 pub async fn cancel(&self, execution_id: &str) -> Result<ExecutionState, String> {
187 if let Some(context) = self.contexts.lock().await.get(execution_id) {
188 context.control.cancellation.cancel();
189 }
190 self.runtime
191 .cancel(execution_id, self.clock.now_ms())
192 .await
193 .map_err(|error| error.to_string())?;
194 self.require(execution_id).await
195 }
196
197 pub async fn start(&self, code: &str) -> Result<ExecutionState, String> {
199 let mut descriptions = Vec::new();
200 for connector in &self.connectors {
201 descriptions.push(connector.describe().await?);
202 }
203 let capabilities = CapabilitySnapshot::new(descriptions)?;
204 let id = self
205 .runtime
206 .begin_with_capabilities(code, capabilities, self.clock.now_ms())
207 .await
208 .map_err(|error| error.to_string())?;
209 self.require(&id).await
210 }
211
212 pub async fn execute(&self, code: &str) -> Result<ExecutionState, String> {
214 self.execute_with(code, CodeModeRunOptions::default()).await
215 }
216
217 pub async fn execute_with(
219 &self,
220 code: &str,
221 options: CodeModeRunOptions,
222 ) -> Result<ExecutionState, String> {
223 let state = self.start(code).await?;
224 self.drive_with(&state.id, options).await
225 }
226
227 pub async fn resume(&self, execution_id: &str) -> Result<ExecutionState, String> {
229 self.resume_with(execution_id, CodeModeRunOptions::default())
230 .await
231 }
232
233 pub async fn resume_with(
235 &self,
236 execution_id: &str,
237 options: CodeModeRunOptions,
238 ) -> Result<ExecutionState, String> {
239 self.runtime
240 .resume(execution_id, self.clock.now_ms())
241 .await
242 .map_err(|error| error.to_string())?;
243 self.drive_with(execution_id, options).await
244 }
245
246 pub async fn approve(&self, execution_id: &str, seq: u64) -> Result<ExecutionState, String> {
248 self.approve_with(execution_id, seq, CodeModeRunOptions::default())
249 .await
250 }
251
252 pub async fn approve_with(
254 &self,
255 execution_id: &str,
256 seq: u64,
257 options: CodeModeRunOptions,
258 ) -> Result<ExecutionState, String> {
259 if !self
260 .runtime
261 .approve(execution_id, seq, self.clock.now_ms())
262 .await
263 .map_err(|error| error.to_string())?
264 {
265 return self.require(execution_id).await;
266 }
267 self.drive_with(execution_id, options).await
268 }
269
270 pub async fn reject(&self, execution_id: &str, seq: u64) -> Result<ExecutionState, String> {
272 if self
273 .runtime
274 .reject(execution_id, seq, self.clock.now_ms())
275 .await
276 .map_err(|error| error.to_string())?
277 {
278 self.notify_execution_end(execution_id, "rejected").await;
279 }
280 self.require(execution_id).await
281 }
282
283 pub async fn rollback(&self, execution_id: &str) -> Result<ExecutionState, String> {
285 for action in self
286 .runtime
287 .actions_to_revert(execution_id)
288 .await
289 .map_err(|error| error.to_string())?
290 {
291 let connector = self
292 .connector(&action.connector)
293 .await?
294 .ok_or_else(|| format!("Connector \"{}\" not found", action.connector))?;
295 if !connector
296 .revert(
297 &action.method,
298 action.arguments,
299 action.result.unwrap_or(Value::Null),
300 &ToolContext {
301 execution_id: execution_id.to_string(),
302 control: Default::default(),
303 request: None,
304 },
305 )
306 .await?
307 {
308 return Err(format!(
309 "{}.{} did not compensate step {}",
310 action.connector, action.method, action.seq
311 ));
312 }
313 self.runtime
314 .mark_reverted(execution_id, action.seq, self.clock.now_ms())
315 .await
316 .map_err(|error| error.to_string())?;
317 }
318 self.runtime
319 .finish_rollback(execution_id, self.clock.now_ms())
320 .await
321 .map_err(|error| error.to_string())?;
322 self.notify_execution_end(execution_id, "rolled_back").await;
323 self.require(execution_id).await
324 }
325
326 pub async fn expire(&self, max_age_ms: u64) -> Result<Vec<String>, String> {
328 let ids = self
329 .runtime
330 .expire(self.clock.now_ms(), max_age_ms)
331 .await
332 .map_err(|error| error.to_string())?;
333 for id in &ids {
334 let status = self.require(id).await?.status;
335 self.notify_execution_end(
336 id,
337 if status == ExecutionStatus::Rejected {
338 "rejected"
339 } else {
340 "error"
341 },
342 )
343 .await;
344 }
345 Ok(ids)
346 }
347
348 pub async fn dispatch(&self, request: DispatchRequest) -> Result<Value, String> {
350 match request {
351 DispatchRequest::Call {
352 execution_id,
353 seq,
354 connector,
355 method,
356 arguments,
357 } => {
358 let session = self.session(&execution_id).await?;
359 serde_json::to_value(
360 session
361 .call_at(seq, &connector, &method, arguments, self.clock.now_ms())
362 .await,
363 )
364 .map_err(|error| error.to_string())
365 }
366 DispatchRequest::BeginStep {
367 execution_id,
368 seq,
369 name,
370 } => {
371 let session = self.session(&execution_id).await?;
372 serde_json::to_value(
373 session
374 .begin_step_at(seq, &name, self.clock.now_ms())
375 .await
376 .map_err(|error| error.to_string())?,
377 )
378 .map_err(|error| error.to_string())
379 }
380 DispatchRequest::RecordStep {
381 execution_id,
382 seq,
383 result,
384 } => {
385 self.runtime
386 .record_result(&execution_id, seq, result, self.clock.now_ms())
387 .await
388 .map_err(|error| error.to_string())?;
389 Ok(serde_json::json!({ "ok": true }))
390 }
391 }
392 }
393
394 pub async fn drive_with(
396 &self,
397 execution_id: &str,
398 options: CodeModeRunOptions,
399 ) -> Result<ExecutionState, String> {
400 let state = self.require(execution_id).await?;
401 if matches!(
402 state.status,
403 ExecutionStatus::Completed
404 | ExecutionStatus::Error
405 | ExecutionStatus::Rejected
406 | ExecutionStatus::RolledBack
407 | ExecutionStatus::Cancelled
408 ) {
409 return Ok(state);
410 }
411 let context = self.context(execution_id, options);
412 let cancellation = context.control.cancellation.clone();
413 self.contexts
414 .lock()
415 .await
416 .insert(execution_id.to_string(), context.clone());
417 let session = match self.session_with_context(execution_id, context).await {
418 Ok(session) => session,
419 Err(error) => {
420 self.contexts.lock().await.remove(execution_id);
421 return Err(error);
422 }
423 };
424 let descriptions = session.descriptions();
425 let execution = self.executor.execute(
426 &state.code,
427 &descriptions,
428 execution_id,
429 Arc::new(ExecutionHost::new(
430 Arc::clone(&session),
431 Arc::clone(&self.clock),
432 )),
433 );
434 tokio::pin!(execution);
435 let response = tokio::select! {
436 _ = cancellation.cancelled() => None,
437 response = &mut execution => Some(response),
438 };
439 self.contexts.lock().await.remove(execution_id);
440 let Some(response) = response else {
441 self.runtime
442 .cancel(execution_id, self.clock.now_ms())
443 .await
444 .map_err(|error| error.to_string())?;
445 session.pass_ended("cancelled").await;
446 session.execution_ended("cancelled").await;
447 return self.require(execution_id).await;
448 };
449 let response = match response {
450 Ok(response) => response,
451 Err(error) => {
452 self.runtime
453 .fail(execution_id, error, Vec::new(), self.clock.now_ms())
454 .await
455 .map_err(|error| error.to_string())?;
456 session.pass_ended("error").await;
457 session.execution_ended("error").await;
458 return self.require(execution_id).await;
459 }
460 };
461 let current = self.require(execution_id).await?;
462 if current.status == ExecutionStatus::Paused {
463 session.pass_ended("paused").await;
464 return Ok(current);
465 }
466 if current.status == ExecutionStatus::Error {
467 session.pass_ended("error").await;
468 session.execution_ended("error").await;
469 return Ok(current);
470 }
471 if let Some(error) = response.error {
472 self.runtime
473 .fail(execution_id, error, response.logs, self.clock.now_ms())
474 .await
475 .map_err(|error| error.to_string())?;
476 session.pass_ended("error").await;
477 session.execution_ended("error").await;
478 } else {
479 self.runtime
480 .complete(
481 execution_id,
482 response.result.unwrap_or(Value::Null),
483 response.logs,
484 self.clock.now_ms(),
485 )
486 .await
487 .map_err(|error| error.to_string())?;
488 session.pass_ended("completed").await;
489 session.execution_ended("completed").await;
490 }
491 self.require(execution_id).await
492 }
493
494 async fn session(&self, execution_id: &str) -> Result<Arc<DispatchSession>, String> {
495 let context = self
496 .contexts
497 .lock()
498 .await
499 .get(execution_id)
500 .cloned()
501 .unwrap_or_else(|| self.context(execution_id, CodeModeRunOptions::default()));
502 self.session_with_context(execution_id, context).await
503 }
504
505 async fn session_with_context(
506 &self,
507 execution_id: &str,
508 context: ToolContext,
509 ) -> Result<Arc<DispatchSession>, String> {
510 let state = self.require(execution_id).await?;
511 let descriptions = if let Some(capabilities) = state.capabilities {
512 capabilities.connectors
513 } else {
514 let mut descriptions = Vec::new();
515 for connector in &self.connectors {
516 descriptions.push(connector.describe().await?);
517 }
518 descriptions
519 };
520 Ok(Arc::new(
521 DispatchSession::new_with_descriptions_and_context(
522 Arc::clone(&self.runtime),
523 context,
524 self.connectors.clone(),
525 descriptions,
526 )
527 .await?,
528 ))
529 }
530
531 fn context(&self, execution_id: &str, options: CodeModeRunOptions) -> ToolContext {
532 ToolContext {
533 execution_id: execution_id.to_string(),
534 control: ToolCallControl {
535 cancellation: options.cancellation,
536 events: Some(Arc::new(RuntimeEventSink {
537 runtime: Arc::clone(&self.runtime),
538 execution_id: execution_id.to_string(),
539 clock: Arc::clone(&self.clock),
540 })),
541 },
542 request: options.request,
543 }
544 }
545
546 async fn require(&self, execution_id: &str) -> Result<ExecutionState, String> {
547 self.runtime
548 .execution(execution_id)
549 .await
550 .map_err(|error| error.to_string())?
551 .ok_or_else(|| format!("Execution \"{execution_id}\" not found"))
552 }
553
554 async fn connector(&self, name: &str) -> Result<Option<&Arc<dyn Connector>>, String> {
555 for connector in &self.connectors {
556 if connector.describe().await?.name == name {
557 return Ok(Some(connector));
558 }
559 }
560 Ok(None)
561 }
562
563 async fn notify_execution_end(&self, execution_id: &str, status: &str) {
564 for connector in &self.connectors {
565 connector.execution_ended(execution_id, status).await;
566 }
567 }
568}