Skip to main content

soma_integrations/
codemode.rs

1//! Implements `soma-application`'s [`CodeModePort`] over `soma-codemode`'s
2//! sandboxed JS snippet runner (plan section 3.20, "CodeModeExecutor" in
3//! section 5's illustrative flow).
4//!
5//! There is exactly one Code Mode execution engine in the workspace
6//! (`soma_codemode::execute::execute_inline`, which spawns the bounded
7//! `soma-codemode-runner` subprocess); this adapter calls it directly rather
8//! than re-implementing any part of the runner, sandbox, or result-shaping
9//! pipeline — the same engine `soma-provider-adapters::codemode` bridges to
10//! for drop-in providers.
11//!
12//! `CodeModeExecuteRequest::input` is not yet threaded into the snippet: the
13//! runner's `Start` protocol message has no side-channel for caller-supplied
14//! input today. This mirrors the identical, already-documented limitation on
15//! `soma_provider_adapters::codemode::CodeModeSnippetProvider`, not a new gap
16//! introduced here.
17//!
18//! `CodeModeConfig::enabled` (default `false`) is checked explicitly by this
19//! adapter before delegating to `execute_inline`, unlike
20//! `soma_provider_adapters::codemode::CodeModeSnippetProvider` and
21//! `execute_inline` itself, neither of which consult the flag. No action,
22//! CLI command, or REST route dispatches to `codemode_execute` yet (see
23//! `crates/soma/domain/src/actions.rs`), so this check has no observable
24//! effect today; it exists so a future PR that wires a live surface to this
25//! port gets a clear `codemode_disabled` error instead of silently running
26//! snippets through a config the operator marked disabled.
27
28use std::sync::{Arc, Mutex};
29
30use async_trait::async_trait;
31use serde_json::{json, Value};
32
33use soma_application::{CodeModeExecuteRequest, CodeModePort, ExecutionContext, PortError};
34use soma_codemode::{execute::execute_inline, CodeModeConfig, ToolError, UiLink};
35
36#[derive(Clone, Default)]
37pub struct CodeModeApplicationPort {
38    config: CodeModeConfig,
39}
40
41impl CodeModeApplicationPort {
42    pub fn new(config: CodeModeConfig) -> Self {
43        Self { config }
44    }
45}
46
47#[async_trait]
48impl CodeModePort for CodeModeApplicationPort {
49    async fn execute(
50        &self,
51        request: CodeModeExecuteRequest,
52        _context: &ExecutionContext,
53    ) -> Result<Value, PortError> {
54        if !self.config.enabled {
55            let mut port_error = PortError::new(
56                "codemode_disabled",
57                "Code Mode is disabled for this instance",
58            );
59            port_error.remediation =
60                "Enable Code Mode in the runtime configuration and retry.".to_owned();
61            return Err(port_error);
62        }
63        let ui_capture: Arc<Mutex<Option<UiLink>>> = Arc::new(Mutex::new(None));
64        let outcome = execute_inline(&request.source, self.config.clone(), ui_capture)
65            .await
66            .map_err(codemode_port_error)?;
67        Ok(json!({
68            "result": outcome.display_response.result,
69            "logs": outcome.display_response.logs,
70        }))
71    }
72}
73
74/// Maps `soma-codemode`'s `ToolError` taxonomy onto a `PortError`, preserving
75/// the distinction between caller mistakes (invalid/missing params, unknown
76/// action), authorization failures, and runner/SDK failures, instead of
77/// collapsing every case into one generic code and remediation string.
78fn codemode_port_error(error: ToolError) -> PortError {
79    let (retryable, remediation): (bool, String) = match &error {
80        ToolError::MissingParam { .. } | ToolError::InvalidParam { .. } => (
81            false,
82            "Fix the Code Mode snippet or its parameters and retry.".to_owned(),
83        ),
84        ToolError::UnknownAction { .. } | ToolError::UnknownInstance { .. } => (
85            false,
86            "Check the referenced action or instance name and retry.".to_owned(),
87        ),
88        ToolError::AmbiguousTool { .. } => (
89            false,
90            "Disambiguate the referenced tool and retry.".to_owned(),
91        ),
92        ToolError::Forbidden {
93            required_scopes, ..
94        } => (
95            false,
96            format!(
97                "Request the required scope(s) and retry: {}",
98                required_scopes.join(", ")
99            ),
100        ),
101        ToolError::Conflict { .. } => (
102            false,
103            "Resolve the conflicting resource and retry.".to_owned(),
104        ),
105        ToolError::ConfirmationRequired { .. } => {
106            (false, "Re-run with explicit confirmation.".to_owned())
107        }
108        ToolError::Sdk { .. } => (
109            true,
110            "Retry; if this persists, check the Code Mode runner's health.".to_owned(),
111        ),
112    };
113    let code = format!("codemode_{}", error.kind());
114    let mut port_error = PortError::new(code, error.to_string());
115    port_error.retryable = retryable;
116    port_error.remediation = remediation;
117    port_error
118}
119
120#[cfg(test)]
121#[path = "codemode_tests.rs"]
122mod tests;