Skip to main content

adk_code/
types.rs

1//! Core execution types for the code execution substrate.
2//!
3//! This module defines the typed primitives shared by all execution backends,
4//! language-preset tools, and Studio integration:
5//!
6//! - [`ExecutionLanguage`] — supported execution languages
7//! - [`ExecutionPayload`] — source code or guest module bytes
8//! - [`ExecutionIsolation`] — backend isolation class
9//! - [`SandboxPolicy`] — requested sandbox controls
10//! - [`BackendCapabilities`] — what a backend can actually enforce
11//! - [`ExecutionRequest`] — full execution request
12//! - [`ExecutionResult`] — structured execution outcome
13//! - [`ExecutionStatus`] — terminal execution status
14
15use serde::{Deserialize, Serialize};
16use serde_json::Value;
17use std::path::PathBuf;
18use std::time::Duration;
19
20/// One megabyte in bytes, used as the default stdout/stderr limit.
21const ONE_MB: usize = 1_048_576;
22
23/// Supported execution languages.
24///
25/// `Rust` is the primary first-class language. Other languages are available
26/// through appropriate backends.
27///
28/// # Example
29///
30/// ```rust
31/// use adk_code::ExecutionLanguage;
32///
33/// let lang = ExecutionLanguage::Rust;
34/// assert_eq!(lang, ExecutionLanguage::Rust);
35/// ```
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub enum ExecutionLanguage {
39    /// Rust — the primary authored-code path.
40    Rust,
41    /// JavaScript — secondary scripting and transform support.
42    JavaScript,
43    /// WebAssembly guest module execution.
44    Wasm,
45    /// Python — in-process Monty execution (`embedded-python` feature) or
46    /// container-backed execution.
47    Python,
48    /// Raw command execution (shell, interpreter, etc.).
49    Command,
50}
51
52impl std::fmt::Display for ExecutionLanguage {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            Self::Rust => write!(f, "Rust"),
56            Self::JavaScript => write!(f, "JavaScript"),
57            Self::Wasm => write!(f, "Wasm"),
58            Self::Python => write!(f, "Python"),
59            Self::Command => write!(f, "Command"),
60        }
61    }
62}
63
64/// Format of a precompiled guest module.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub enum GuestModuleFormat {
67    /// WebAssembly binary format.
68    Wasm,
69}
70
71/// The code or module to execute.
72///
73/// Source payloads carry inline code strings. Guest module payloads carry
74/// precompiled binary modules (e.g., `.wasm` files).
75///
76/// # Example
77///
78/// ```rust
79/// use adk_code::ExecutionPayload;
80///
81/// let payload = ExecutionPayload::Source {
82///     code: "fn run(input: serde_json::Value) -> serde_json::Value { input }".to_string(),
83/// };
84/// ```
85#[derive(Debug, Clone)]
86pub enum ExecutionPayload {
87    /// Inline source code to compile and/or interpret.
88    Source {
89        /// The source code string.
90        code: String,
91    },
92    /// A precompiled guest module (e.g., WASM).
93    GuestModule {
94        /// The binary format of the guest module.
95        format: GuestModuleFormat,
96        /// The raw module bytes.
97        bytes: Vec<u8>,
98    },
99}
100
101/// Backend isolation class.
102///
103/// Makes the isolation model explicit so that host-local and container-backed
104/// execution cannot be presented as equivalent.
105///
106/// # Example
107///
108/// ```rust
109/// use adk_code::ExecutionIsolation;
110///
111/// let iso = ExecutionIsolation::ContainerEphemeral;
112/// assert_ne!(iso, ExecutionIsolation::HostLocal);
113/// ```
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub enum ExecutionIsolation {
117    /// Execution runs in the same process (e.g., embedded JS engine).
118    InProcess,
119    /// Execution runs as a local host process without strong OS isolation.
120    HostLocal,
121    /// Execution runs in an ephemeral container destroyed after completion.
122    ContainerEphemeral,
123    /// Execution runs in a persistent container that survives across requests.
124    ContainerPersistent,
125    /// Execution runs on a remote provider-hosted service.
126    ProviderHosted,
127}
128
129/// Network access policy for sandboxed execution.
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
131pub enum NetworkPolicy {
132    /// No network access allowed.
133    Disabled,
134    /// Network access is permitted.
135    Enabled,
136}
137
138/// Filesystem access policy for sandboxed execution.
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub enum FilesystemPolicy {
141    /// No filesystem access.
142    None,
143    /// Read-only access to a workspace root.
144    WorkspaceReadOnly {
145        /// The workspace root path.
146        root: PathBuf,
147    },
148    /// Read-write access to a workspace root.
149    WorkspaceReadWrite {
150        /// The workspace root path.
151        root: PathBuf,
152    },
153    /// Explicit path-level access control.
154    Paths {
155        /// Paths with read-only access.
156        read_only: Vec<PathBuf>,
157        /// Paths with read-write access.
158        read_write: Vec<PathBuf>,
159    },
160}
161
162/// Environment variable access policy for sandboxed execution.
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub enum EnvironmentPolicy {
165    /// No environment variables exposed.
166    None,
167    /// Only the listed environment variable names are exposed.
168    AllowList(Vec<String>),
169}
170
171/// Sandbox policy describing the requested execution constraints.
172///
173/// Backends compare this policy against their [`BackendCapabilities`] and
174/// reject execution if they cannot enforce a requested control.
175///
176/// # Example
177///
178/// ```rust
179/// use adk_code::SandboxPolicy;
180///
181/// let policy = SandboxPolicy::strict_rust();
182/// assert_eq!(policy.max_stdout_bytes, 1_048_576);
183/// ```
184#[derive(Debug, Clone)]
185pub struct SandboxPolicy {
186    /// Network access policy.
187    pub network: NetworkPolicy,
188    /// Filesystem access policy.
189    pub filesystem: FilesystemPolicy,
190    /// Environment variable access policy.
191    pub environment: EnvironmentPolicy,
192    /// Maximum execution duration.
193    pub timeout: Duration,
194    /// Maximum bytes captured from stdout before truncation.
195    pub max_stdout_bytes: usize,
196    /// Maximum bytes captured from stderr before truncation.
197    pub max_stderr_bytes: usize,
198    /// Working directory for execution, if any.
199    pub working_directory: Option<PathBuf>,
200}
201
202impl SandboxPolicy {
203    /// Strict policy for Rust sandbox execution.
204    ///
205    /// - No network access
206    /// - No filesystem access
207    /// - No environment variables
208    /// - 30-second timeout
209    /// - 1 MB stdout/stderr limits
210    pub fn strict_rust() -> Self {
211        Self {
212            network: NetworkPolicy::Disabled,
213            filesystem: FilesystemPolicy::None,
214            environment: EnvironmentPolicy::None,
215            timeout: Duration::from_secs(30),
216            max_stdout_bytes: ONE_MB,
217            max_stderr_bytes: ONE_MB,
218            working_directory: None,
219        }
220    }
221
222    /// Host-local policy for backends that run on the host without isolation.
223    ///
224    /// Unlike [`strict_rust`](Self::strict_rust), this policy uses
225    /// `NetworkPolicy::Enabled` and `FilesystemPolicy::None` so that
226    /// host-local backends (which cannot enforce network or filesystem
227    /// restrictions) pass policy validation. The trade-off is that the
228    /// executed code has the same network and filesystem access as the
229    /// host process.
230    ///
231    /// - Network access: allowed (host-local cannot restrict)
232    /// - Filesystem access: none requested
233    /// - Environment variables: none exposed
234    /// - 30-second timeout
235    /// - 1 MB stdout/stderr limits
236    pub fn host_local() -> Self {
237        Self {
238            network: NetworkPolicy::Enabled,
239            filesystem: FilesystemPolicy::None,
240            environment: EnvironmentPolicy::None,
241            timeout: Duration::from_secs(30),
242            max_stdout_bytes: ONE_MB,
243            max_stderr_bytes: ONE_MB,
244            working_directory: None,
245        }
246    }
247
248    /// Strict policy for embedded JavaScript execution.
249    ///
250    /// Same defaults as Rust but with a shorter 5-second timeout,
251    /// appropriate for lightweight transforms and scripting.
252    pub fn strict_js() -> Self {
253        Self {
254            network: NetworkPolicy::Disabled,
255            filesystem: FilesystemPolicy::None,
256            environment: EnvironmentPolicy::None,
257            timeout: Duration::from_secs(5),
258            max_stdout_bytes: ONE_MB,
259            max_stderr_bytes: ONE_MB,
260            working_directory: None,
261        }
262    }
263
264    /// Strict policy for embedded Python (Monty) execution.
265    ///
266    /// - No network access (Monty has no network surface regardless)
267    /// - No filesystem access
268    /// - No environment variables
269    /// - 30-second timeout
270    /// - 1 MB stdout/stderr limits
271    pub fn strict_python() -> Self {
272        Self {
273            network: NetworkPolicy::Disabled,
274            filesystem: FilesystemPolicy::None,
275            environment: EnvironmentPolicy::None,
276            timeout: Duration::from_secs(30),
277            max_stdout_bytes: ONE_MB,
278            max_stderr_bytes: ONE_MB,
279            working_directory: None,
280        }
281    }
282}
283
284impl Default for SandboxPolicy {
285    /// Sensible defaults: no network, no filesystem, no env vars, 30s timeout, 1 MB limits.
286    fn default() -> Self {
287        Self::strict_rust()
288    }
289}
290
291/// Capabilities that a backend can actually enforce.
292///
293/// This makes the isolation model explicit so callers and docs can distinguish
294/// what a backend claims from what it can guarantee.
295///
296/// # Example
297///
298/// ```rust
299/// use adk_code::{BackendCapabilities, ExecutionIsolation};
300///
301/// let caps = BackendCapabilities {
302///     isolation: ExecutionIsolation::ContainerEphemeral,
303///     enforce_network_policy: true,
304///     enforce_filesystem_policy: true,
305///     enforce_environment_policy: true,
306///     enforce_timeout: true,
307///     supports_structured_output: true,
308///     supports_process_execution: false,
309///     supports_persistent_workspace: false,
310///     supports_interactive_sessions: false,
311/// };
312/// assert!(caps.enforce_network_policy);
313/// ```
314#[derive(Debug, Clone, PartialEq, Eq)]
315pub struct BackendCapabilities {
316    /// The isolation class this backend provides.
317    pub isolation: ExecutionIsolation,
318    /// Whether the backend can enforce network restrictions.
319    pub enforce_network_policy: bool,
320    /// Whether the backend can enforce filesystem restrictions.
321    pub enforce_filesystem_policy: bool,
322    /// Whether the backend can enforce environment variable restrictions.
323    pub enforce_environment_policy: bool,
324    /// Whether the backend can enforce execution timeouts.
325    pub enforce_timeout: bool,
326    /// Whether the backend supports structured JSON output.
327    pub supports_structured_output: bool,
328    /// Whether the backend supports spawning child processes.
329    pub supports_process_execution: bool,
330    /// Whether the backend supports persistent workspaces across requests.
331    pub supports_persistent_workspace: bool,
332    /// Whether the backend supports interactive/REPL-style sessions.
333    pub supports_interactive_sessions: bool,
334}
335
336/// A full execution request.
337///
338/// Combines language, payload, sandbox policy, optional I/O, and identity
339/// into a single typed request that backends can validate and execute.
340///
341/// # Example
342///
343/// ```rust
344/// use adk_code::{ExecutionRequest, ExecutionLanguage, ExecutionPayload, SandboxPolicy};
345///
346/// let request = ExecutionRequest {
347///     language: ExecutionLanguage::Rust,
348///     payload: ExecutionPayload::Source {
349///         code: r#"fn run(input: serde_json::Value) -> serde_json::Value { input }"#.to_string(),
350///     },
351///     argv: vec![],
352///     stdin: None,
353///     input: None,
354///     sandbox: SandboxPolicy::strict_rust(),
355///     identity: None,
356/// };
357/// ```
358#[derive(Debug, Clone)]
359pub struct ExecutionRequest {
360    /// The target execution language.
361    pub language: ExecutionLanguage,
362    /// The code or module to execute.
363    pub payload: ExecutionPayload,
364    /// Command-line arguments passed to the executed program.
365    pub argv: Vec<String>,
366    /// Optional stdin bytes fed to the executed program.
367    pub stdin: Option<Vec<u8>>,
368    /// Optional structured JSON input injected through a controlled harness.
369    pub input: Option<Value>,
370    /// The sandbox policy for this execution.
371    pub sandbox: SandboxPolicy,
372    /// Optional execution identity for audit and telemetry correlation.
373    pub identity: Option<String>,
374}
375
376/// Terminal status of an execution.
377///
378/// Distinguishes compile failures from runtime failures, timeouts, and rejections.
379///
380/// # Example
381///
382/// ```rust
383/// use adk_code::ExecutionStatus;
384///
385/// let status = ExecutionStatus::CompileFailed;
386/// assert_ne!(status, ExecutionStatus::Failed);
387/// ```
388#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
389#[serde(rename_all = "camelCase")]
390pub enum ExecutionStatus {
391    /// Execution completed successfully.
392    Success,
393    /// Execution exceeded the configured timeout.
394    Timeout,
395    /// Compilation or build step failed (distinct from runtime failure).
396    CompileFailed,
397    /// Runtime execution failed.
398    Failed,
399    /// Execution was rejected before running (policy or scope check).
400    Rejected,
401}
402
403/// Execution metadata for telemetry, audit, and artifact correlation.
404///
405/// Captures backend name, language, duration, status, and correlation identity
406/// so that executions can be traced and audited across sessions and invocations.
407///
408/// # Example
409///
410/// ```rust
411/// use adk_code::{ExecutionMetadata, ExecutionLanguage, ExecutionStatus, ExecutionIsolation};
412///
413/// let meta = ExecutionMetadata {
414///     backend_name: "rust-sandbox".to_string(),
415///     language: ExecutionLanguage::Rust,
416///     isolation: ExecutionIsolation::HostLocal,
417///     status: ExecutionStatus::Success,
418///     duration_ms: 42,
419///     identity: Some("inv-123".to_string()),
420///     artifact_refs: vec![],
421/// };
422/// assert_eq!(meta.backend_name, "rust-sandbox");
423/// ```
424#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
425#[serde(rename_all = "camelCase")]
426pub struct ExecutionMetadata {
427    /// Name of the backend that executed the request.
428    pub backend_name: String,
429    /// Language that was executed.
430    pub language: ExecutionLanguage,
431    /// Isolation class of the backend.
432    pub isolation: ExecutionIsolation,
433    /// Terminal execution status.
434    pub status: ExecutionStatus,
435    /// Execution wall-clock duration in milliseconds.
436    pub duration_ms: u64,
437    /// Correlation identity (invocation ID, session ID, etc.) when available.
438    pub identity: Option<String>,
439    /// References to artifacts stored externally (e.g., large outputs).
440    pub artifact_refs: Vec<ArtifactRef>,
441}
442
443/// Reference to an externally stored artifact.
444///
445/// When execution output exceeds inline size limits, the result can reference
446/// artifacts stored through ADK artifact mechanisms instead of forcing large
447/// binary data into inline JSON strings.
448///
449/// # Example
450///
451/// ```rust
452/// use adk_code::ArtifactRef;
453///
454/// let artifact = ArtifactRef {
455///     key: "stdout-full".to_string(),
456///     size_bytes: 2_000_000,
457///     content_type: Some("text/plain".to_string()),
458/// };
459/// assert_eq!(artifact.key, "stdout-full");
460/// ```
461#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
462#[serde(rename_all = "camelCase")]
463pub struct ArtifactRef {
464    /// Artifact storage key or identifier.
465    pub key: String,
466    /// Size of the artifact in bytes.
467    pub size_bytes: u64,
468    /// MIME content type, if known.
469    pub content_type: Option<String>,
470}
471
472/// Structured result of a code execution.
473///
474/// Captures stdout, stderr, structured output, truncation flags, exit code,
475/// duration, and optional execution metadata so downstream consumers can
476/// reason about outcomes reliably.
477///
478/// # Example
479///
480/// ```rust
481/// use adk_code::{ExecutionResult, ExecutionStatus};
482///
483/// let result = ExecutionResult {
484///     status: ExecutionStatus::Success,
485///     stdout: "hello\n".to_string(),
486///     stderr: String::new(),
487///     output: Some(serde_json::json!({ "answer": 42 })),
488///     exit_code: Some(0),
489///     stdout_truncated: false,
490///     stderr_truncated: false,
491///     duration_ms: 37,
492///     metadata: None,
493/// };
494/// assert_eq!(result.status, ExecutionStatus::Success);
495/// ```
496#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
497#[serde(rename_all = "camelCase")]
498pub struct ExecutionResult {
499    /// Terminal execution status.
500    pub status: ExecutionStatus,
501    /// Captured stdout text (may be truncated).
502    pub stdout: String,
503    /// Captured stderr text (may be truncated).
504    pub stderr: String,
505    /// Optional structured JSON output from the executed code.
506    pub output: Option<Value>,
507    /// Process exit code, if available.
508    pub exit_code: Option<i32>,
509    /// Whether stdout was truncated due to size limits.
510    pub stdout_truncated: bool,
511    /// Whether stderr was truncated due to size limits.
512    pub stderr_truncated: bool,
513    /// Execution wall-clock duration in milliseconds.
514    pub duration_ms: u64,
515    /// Optional execution metadata for telemetry and audit.
516    #[serde(skip_serializing_if = "Option::is_none")]
517    pub metadata: Option<ExecutionMetadata>,
518}