use super::*;
pub const E2B_PLATFORM_TEMPLATE_ID: &str = "code-interpreter-v1";
pub const E2B_BUILTIN_TEMPLATE_IDS: [&str; 3] = [
E2B_PLATFORM_TEMPLATE_ID,
"context-management-moc",
"vpc-sandbox",
];
pub const MOULIN_PLATFORM_SNAPSHOT_ID: &str = "python";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum SnapshotVisibility {
#[default]
Private,
Public,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum SnapshotBuildSpec {
Dockerfile {
dockerfile: String,
},
Template {
#[serde(alias = "pipPackages")]
pip_packages: String,
},
Git {
#[serde(rename = "gitUrl", alias = "git_url")]
git_url: String,
#[serde(default, rename = "commitSha", alias = "commit_sha")]
commit_sha: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WorkspaceBuildProvider {
Moulin,
E2b,
Custom(String),
}
impl WorkspaceBuildProvider {
pub fn parse(value: &str) -> Result<Self> {
match value {
"moulin" => Ok(Self::Moulin),
"e2b" => Ok(Self::E2b),
other if !other.trim().is_empty() => Ok(Self::Custom(other.into())),
_ => Err(anyhow::anyhow!("Workspace build provider is required")),
}
}
pub fn as_str(&self) -> &str {
match self {
Self::Moulin => "moulin",
Self::E2b => "e2b",
Self::Custom(value) => value,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MoulinImageBuildRecipe {
Dockerfile { dockerfile: String },
Git { git_url: String, commit_sha: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum E2bImageBuildRecipe {
Dockerfile { dockerfile: String },
Template { pip_packages: String },
Git { git_url: String, commit_sha: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EnvironmentImageBuildRequest {
Moulin(MoulinImageBuildRecipe),
E2b(E2bImageBuildRecipe),
Custom {
provider: String,
recipe: SnapshotBuildSpec,
},
}
impl EnvironmentImageBuildRequest {
pub fn from_parts(provider: &str, spec: &SnapshotBuildSpec) -> Result<Self> {
match (WorkspaceBuildProvider::parse(provider)?, spec) {
(WorkspaceBuildProvider::Moulin, SnapshotBuildSpec::Dockerfile { dockerfile }) => {
Ok(Self::Moulin(MoulinImageBuildRecipe::Dockerfile {
dockerfile: dockerfile.clone(),
}))
}
(
WorkspaceBuildProvider::Moulin,
SnapshotBuildSpec::Git {
git_url,
commit_sha,
},
) => Ok(Self::Moulin(MoulinImageBuildRecipe::Git {
git_url: git_url.clone(),
commit_sha: commit_sha.clone(),
})),
(WorkspaceBuildProvider::E2b, SnapshotBuildSpec::Dockerfile { dockerfile }) => {
Ok(Self::E2b(E2bImageBuildRecipe::Dockerfile {
dockerfile: dockerfile.clone(),
}))
}
(WorkspaceBuildProvider::E2b, SnapshotBuildSpec::Template { pip_packages }) => {
Ok(Self::E2b(E2bImageBuildRecipe::Template {
pip_packages: pip_packages.clone(),
}))
}
(
WorkspaceBuildProvider::E2b,
SnapshotBuildSpec::Git {
git_url,
commit_sha,
},
) => Ok(Self::E2b(E2bImageBuildRecipe::Git {
git_url: git_url.clone(),
commit_sha: commit_sha.clone(),
})),
(WorkspaceBuildProvider::Moulin, SnapshotBuildSpec::Template { .. }) => Err(
anyhow::anyhow!("Moulin does not support E2B template image recipes"),
),
(WorkspaceBuildProvider::Custom(provider), recipe) => Ok(Self::Custom {
provider,
recipe: recipe.clone(),
}),
}
}
pub fn provider(&self) -> WorkspaceBuildProvider {
match self {
Self::Moulin(_) => WorkspaceBuildProvider::Moulin,
Self::E2b(_) => WorkspaceBuildProvider::E2b,
Self::Custom { provider, .. } => WorkspaceBuildProvider::Custom(provider.clone()),
}
}
pub fn provider_name(&self) -> &str {
match self {
Self::Moulin(_) => "moulin",
Self::E2b(_) => "e2b",
Self::Custom { provider, .. } => provider,
}
}
pub fn snapshot_spec(&self) -> SnapshotBuildSpec {
match self {
Self::Moulin(MoulinImageBuildRecipe::Dockerfile { dockerfile })
| Self::E2b(E2bImageBuildRecipe::Dockerfile { dockerfile }) => {
SnapshotBuildSpec::Dockerfile {
dockerfile: dockerfile.clone(),
}
}
Self::E2b(E2bImageBuildRecipe::Template { pip_packages }) => {
SnapshotBuildSpec::Template {
pip_packages: pip_packages.clone(),
}
}
Self::Moulin(MoulinImageBuildRecipe::Git {
git_url,
commit_sha,
})
| Self::E2b(E2bImageBuildRecipe::Git {
git_url,
commit_sha,
}) => SnapshotBuildSpec::Git {
git_url: git_url.clone(),
commit_sha: commit_sha.clone(),
},
Self::Custom { recipe, .. } => recipe.clone(),
}
}
}
pub const SNAPSHOT_DOCKERFILE_MAX_BYTES: usize = 1024 * 1024;
pub const SNAPSHOT_TEMPLATE_PACKAGES_MAX_BYTES: usize = 64 * 1024;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct EnvironmentCatalogConfig {
pub default_snapshot: Option<crate::DefaultExecutionSnapshot>,
pub default_provider: Option<String>,
pub default_resources: SandboxResources,
pub default_builds: BTreeMap<String, SnapshotBuildSpec>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct CreateSnapshotResourceRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub alias: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(default)]
pub visibility: SnapshotVisibility,
#[serde(default)]
pub owners: Vec<OwnerRef>,
#[serde(default)]
pub builtin: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build: Option<SnapshotBuildSpec>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct BuildBuiltinSnapshotRequest {
pub provider: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub git_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub commit_sha: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dockerfile: Option<String>,
}
pub const ADMIN_BUILD_BUILTIN_SNAPSHOTS_PATH: &str = "/admin/api/workspace-snapshots:build-builtin";
pub const ADMIN_SNAPSHOTS_PATH: &str = "/admin/api/workspace-snapshots";
pub const ADMIN_SNAPSHOT_PATH: &str = "/admin/api/workspace-snapshots/{snapshotId}";
pub const CATALOG_SNAPSHOT_PATH: &str = "/internal/v1/workspace/snapshots:catalog/{snapshotId}";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ProviderBuildHandle {
pub build_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource_external_id: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct EnvironmentBuildObservation {
pub stage: Option<String>,
pub logs: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EnvironmentBuildStatus {
Building(ProviderBuildHandle),
BuildingObserved {
handle: ProviderBuildHandle,
observation: EnvironmentBuildObservation,
},
Ready(ProviderResourceRef),
ReadyWithMetadata {
provider_ref: ProviderResourceRef,
digest: Option<String>,
size_bytes: Option<u64>,
},
ReadyObserved {
provider_ref: ProviderResourceRef,
digest: Option<String>,
size_bytes: Option<u64>,
observation: EnvironmentBuildObservation,
},
Failed(String),
FailedObserved {
error: String,
observation: EnvironmentBuildObservation,
},
}
#[async_trait]
pub trait EnvironmentBuildPort: Send + Sync + Debug {
async fn submit_environment_image(
&self,
request: &EnvironmentImageBuildRequest,
idempotency_key: &str,
) -> Result<EnvironmentBuildStatus>;
async fn poll_environment_image(
&self,
_provider: WorkspaceBuildProvider,
_handle: &ProviderBuildHandle,
) -> Result<EnvironmentBuildStatus> {
Err(anyhow::anyhow!(
"build provider does not support asynchronous status observation"
))
}
}