Skip to main content

rs_adk/code_executors/
unsafe_local.rs

1//! Unsafe local code executor — runs code directly on the host.
2//!
3//! Mirrors ADK-Python's `unsafe_local_code_executor`. Runs Python code
4//! directly on the host machine without any sandboxing.
5//!
6//! **WARNING**: This executor provides NO isolation. Only use in
7//! trusted development environments.
8
9use async_trait::async_trait;
10
11use super::base::{CodeExecutor, CodeExecutorError};
12use super::types::{CodeExecutionInput, CodeExecutionResult};
13
14/// Code executor that runs Python code directly on the host.
15///
16/// # Safety
17///
18/// This executor provides **NO sandboxing**. Code runs with the same
19/// permissions as the host process. Only use in trusted development
20/// environments where code is known to be safe.
21#[derive(Debug, Clone, Default)]
22pub struct UnsafeLocalCodeExecutor {
23    /// Execution timeout in seconds.
24    timeout_secs: u64,
25}
26
27impl UnsafeLocalCodeExecutor {
28    /// Create a new unsafe local executor with a 30-second timeout.
29    pub fn new() -> Self {
30        Self { timeout_secs: 30 }
31    }
32
33    /// Set the execution timeout.
34    pub fn with_timeout(mut self, timeout_secs: u64) -> Self {
35        self.timeout_secs = timeout_secs;
36        self
37    }
38}
39
40#[async_trait]
41impl CodeExecutor for UnsafeLocalCodeExecutor {
42    async fn execute_code(
43        &self,
44        input: CodeExecutionInput,
45    ) -> Result<CodeExecutionResult, CodeExecutorError> {
46        let output = tokio::time::timeout(
47            std::time::Duration::from_secs(self.timeout_secs),
48            tokio::process::Command::new("python3")
49                .arg("-c")
50                .arg(&input.code)
51                .output(),
52        )
53        .await
54        .map_err(|_| CodeExecutorError::Timeout(self.timeout_secs))?
55        .map_err(|e| CodeExecutorError::ExecutionFailed(format!("Local execution failed: {e}")))?;
56
57        Ok(CodeExecutionResult {
58            stdout: String::from_utf8_lossy(&output.stdout).to_string(),
59            stderr: String::from_utf8_lossy(&output.stderr).to_string(),
60            output_files: vec![],
61        })
62    }
63
64    fn stateful(&self) -> bool {
65        false
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn default_timeout() {
75        let exec = UnsafeLocalCodeExecutor::new();
76        assert_eq!(exec.timeout_secs, 30);
77    }
78
79    #[test]
80    fn custom_timeout() {
81        let exec = UnsafeLocalCodeExecutor::new().with_timeout(60);
82        assert_eq!(exec.timeout_secs, 60);
83    }
84
85    #[test]
86    fn not_stateful() {
87        let exec = UnsafeLocalCodeExecutor::new();
88        assert!(!exec.stateful());
89    }
90}