Skip to main content

everruns_integrations_deno/
lib.rs

1//! Deno Sandbox Integration.
2//!
3//! Cloud-based sandboxed code execution via Deno Sandboxes.
4//! Supports multiple sandboxes per session, each identified by `sandbox_id`.
5//!
6//! Decision: model the public tool surface after Daytona so agents can switch
7//! between cloud sandboxes with minimal prompt changes.
8//! Decision: BYO connection model — credentials resolve from user connection only;
9//!   no env-var fallback. Fail with ConnectionRequired if not configured.
10//! Decision: create sandboxes with a fixed timeout instead of Deno's default
11//! `session` lifetime because Everruns closes the creator websocket after each tool.
12
13pub mod client;
14pub mod connection;
15pub mod state;
16mod tools;
17
18use everruns_core::LEASED_RESOURCES_FEATURE;
19use everruns_core::capabilities::{
20    Capability, CapabilityLocalization, CapabilityStatus, IntegrationPlugin, RiskLevel,
21};
22use everruns_core::connector::ConnectorPlugin;
23use everruns_core::tools::Tool;
24use std::sync::LazyLock;
25use std::time::Duration;
26
27use connection::DenoConnector;
28use tools::{
29    DenoCreateSandboxTool, DenoExecTool, DenoListSandboxesTool, DenoManageSandboxTool,
30    DenoReadFileTool, DenoWriteFileTool,
31};
32
33inventory::submit! {
34    IntegrationPlugin {
35        experimental_only: false,
36        feature_flag: None,
37        factory: || Box::new(DenoCapability),
38    }
39}
40
41inventory::submit! {
42    ConnectorPlugin {
43        experimental_only: true,
44        factory: || Box::new(DenoConnector),
45    }
46}
47
48const DENO_CONSOLE_API_BASE: &str = "https://console.deno.com";
49const DENO_SANDBOX_BASE_DOMAIN: &str = "sandbox-api.deno.net";
50const DENO_SANDBOX_SECRET_PREFIX: &str = "deno_sandbox:";
51const DENO_SANDBOX_TIMEOUT: &str = "20m";
52const DENO_DEFAULT_MEMORY_MB: u64 = 1_280;
53const DENO_MAX_MEMORY_MB: u64 = 16 * 1_024;
54const DENO_RPC_TIMEOUT: Duration = Duration::from_secs(30);
55const DENO_STREAM_IDLE_TIMEOUT: Duration = Duration::from_secs(2);
56const DENO_WORKSPACE_PATH: &str = "/home/app";
57
58static SYSTEM_PROMPT: LazyLock<String> = LazyLock::new(|| {
59    let mut prompt = String::from(
60        "Deno sandboxes are isolated networked microVMs. Create or select a sandbox before sandbox-scoped operations, use `/home/app` as the default workspace, and delete sandboxes when done to avoid leaks.",
61    );
62    prompt.push_str(everruns_core::tool_output_sanitizer::EXEC_OUTPUT_HINT);
63    prompt
64});
65
66pub struct DenoCapability;
67
68impl Capability for DenoCapability {
69    fn id(&self) -> &str {
70        "deno"
71    }
72
73    fn name(&self) -> &str {
74        "Deno Sandboxes"
75    }
76
77    fn description(&self) -> &str {
78        "Run code in cloud-based Deno sandboxes. Create isolated Linux microVMs, execute commands, and manage files. EXPERIMENTAL: This capability may change."
79    }
80
81    fn localizations(&self) -> Vec<CapabilityLocalization> {
82        vec![CapabilityLocalization::text(
83            "uk",
84            "Пісочниці Deno",
85            "Запускайте код у хмарних пісочницях Deno. Створюйте ізольовані мікровіртуальні \
86             машини Linux, виконуйте команди та керуйте файлами. ЕКСПЕРИМЕНТАЛЬНО: ця \
87             можливість може змінитися.",
88        )]
89    }
90
91    fn status(&self) -> CapabilityStatus {
92        CapabilityStatus::Available
93    }
94
95    fn risk_level(&self) -> RiskLevel {
96        RiskLevel::High
97    }
98
99    fn icon(&self) -> Option<&str> {
100        Some("server")
101    }
102
103    fn category(&self) -> Option<&str> {
104        Some("Sandboxes")
105    }
106
107    fn system_prompt_addition(&self) -> Option<&str> {
108        Some(&SYSTEM_PROMPT)
109    }
110
111    fn tools(&self) -> Vec<Box<dyn Tool>> {
112        vec![
113            Box::new(DenoCreateSandboxTool),
114            Box::new(DenoExecTool),
115            Box::new(DenoReadFileTool),
116            Box::new(DenoWriteFileTool),
117            Box::new(DenoListSandboxesTool),
118            Box::new(DenoManageSandboxTool),
119        ]
120    }
121
122    fn dependencies(&self) -> Vec<&'static str> {
123        vec!["session_storage"]
124    }
125
126    fn features(&self) -> Vec<&'static str> {
127        vec![LEASED_RESOURCES_FEATURE]
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use everruns_core::capabilities::CapabilityStatus;
135
136    #[test]
137    fn test_capability_metadata() {
138        let cap = DenoCapability;
139        assert_eq!(cap.id(), "deno");
140        assert_eq!(cap.name(), "Deno Sandboxes");
141        assert_eq!(cap.status(), CapabilityStatus::Available);
142        assert_eq!(cap.icon(), Some("server"));
143        assert_eq!(cap.category(), Some("Sandboxes"));
144    }
145
146    #[test]
147    fn test_capability_has_all_tools() {
148        let cap = DenoCapability;
149        let tools = cap.tools();
150        assert_eq!(tools.len(), 6);
151
152        let names: Vec<&str> = tools.iter().map(|t| t.name()).collect();
153        assert!(names.contains(&"deno_create_sandbox"));
154        assert!(names.contains(&"deno_exec"));
155        assert!(names.contains(&"deno_read_file"));
156        assert!(names.contains(&"deno_write_file"));
157        assert!(names.contains(&"deno_list_sandboxes"));
158        assert!(names.contains(&"deno_manage_sandbox"));
159    }
160
161    #[tokio::test]
162    async fn system_prompt_within_budget() {
163        let cap = DenoCapability;
164        let ctx = everruns_core::capabilities::SystemPromptContext::without_file_store(
165            everruns_core::SessionId::new(),
166        );
167        let prompt = cap.system_prompt_contribution(&ctx).await.unwrap();
168        // Bumped 1000 → 1300: EVE-778 grew the shared EXEC_OUTPUT_HINT with the
169        // single-read/contextual-search policy (+438 bytes), taking this
170        // contribution to 1256 bytes.
171        assert!(prompt.len() <= 1300, "prompt is {} bytes", prompt.len());
172    }
173}