mod client;
mod executor;
mod tool;
mod types;
pub use client::VertexSandboxClient;
pub use executor::SandboxCodeExecutor;
pub use tool::VertexSandboxTool;
pub use types::{
Chunk, ChunkMetadata, CodeExecutionEnvironment, CodeLanguage, ComputerUseEnvironment,
CreateSandboxRequest, InputFile, MachineConfig, OutputFile, SandboxEnvironment,
SandboxEnvironmentSpec, SandboxExecutionResult, SandboxState, decode_output_chunks,
encode_code_chunk, encode_file_chunk,
};
use adk_core::{ErrorComponent, Result};
use adk_gcp::{GcpErrorCodes, GcpErrorContext};
use std::time::Duration;
const CODES: GcpErrorCodes = GcpErrorCodes {
invalid_input: "code.vertex_sandbox.invalid_input",
unauthorized: "code.vertex_sandbox.unauthorized",
forbidden: "code.vertex_sandbox.forbidden",
not_found: "code.vertex_sandbox.not_found",
rate_limited: "code.vertex_sandbox.rate_limited",
timeout: "code.vertex_sandbox.timeout",
unavailable: "code.vertex_sandbox.unavailable",
credentials_unavailable: "code.vertex_sandbox.credentials_unavailable",
invalid_response: "code.vertex_sandbox.invalid_response",
invalid_request: "code.vertex_sandbox.invalid_request",
upstream_error: "code.vertex_sandbox.upstream_error",
operation_failed: "code.vertex_sandbox.operation_failed",
};
pub(crate) fn errors() -> GcpErrorContext {
GcpErrorContext::new(ErrorComponent::Code, CODES, "vertex sandbox")
}
pub const MAX_REQUEST_FILE_BYTES: usize = 100 * 1024 * 1024;
const MAX_RESPONSE_BYTES: usize = 140 * 1024 * 1024;
const HTTP_REQUEST_TIMEOUT: Duration = Duration::from_secs(300);
const CREATE_POLL_TIMEOUT: Duration = Duration::from_secs(300);
pub const DEFAULT_SANDBOX_DISPLAY_NAME: &str = "default_sandbox";
pub const DEFAULT_SANDBOX_TTL: &str = "31536000s";
const ENV_GOOGLE_CLOUD_PROJECT: &str = "GOOGLE_CLOUD_PROJECT";
const ENV_GOOGLE_CLOUD_LOCATION: &str = "GOOGLE_CLOUD_LOCATION";
#[derive(Debug, Clone)]
pub struct VertexSandboxConfig {
pub(crate) project_id: String,
pub(crate) location: String,
pub(crate) endpoint: Option<String>,
}
impl VertexSandboxConfig {
pub fn new(project_id: impl Into<String>, location: impl Into<String>) -> Self {
Self { project_id: project_id.into(), location: location.into(), endpoint: None }
}
pub fn from_env() -> Result<Self> {
let read = |key: &str| {
std::env::var(key)
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
};
let project_id = read(ENV_GOOGLE_CLOUD_PROJECT);
let location = read(ENV_GOOGLE_CLOUD_LOCATION);
match (project_id, location) {
(Some(project_id), Some(location)) => Ok(Self::new(project_id, location)),
(project_id, location) => {
let missing = [
(ENV_GOOGLE_CLOUD_PROJECT, project_id.is_none()),
(ENV_GOOGLE_CLOUD_LOCATION, location.is_none()),
]
.into_iter()
.filter_map(|(key, is_missing)| is_missing.then_some(key))
.collect::<Vec<_>>()
.join(", ");
Err(errors().invalid_input(format!(
"missing or blank environment variable(s): {missing}. The Agent Engine platform sets these inside deployed containers; set them explicitly elsewhere, or construct the config with VertexSandboxConfig::new",
)))
}
}
}
#[must_use]
pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = Some(endpoint.into());
self
}
pub(crate) fn endpoint(&self) -> String {
self.endpoint
.clone()
.unwrap_or_else(|| format!("https://{}-aiplatform.googleapis.com", self.location))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn endpoint_defaults_to_regional_origin() {
let config = VertexSandboxConfig::new("p", "europe-west1");
assert_eq!(config.endpoint(), "https://europe-west1-aiplatform.googleapis.com");
let overridden = config.with_endpoint("http://127.0.0.1:8080");
assert_eq!(overridden.endpoint(), "http://127.0.0.1:8080");
}
}