1use super::*;
4
5pub const E2B_PLATFORM_TEMPLATE_ID: &str = "code-interpreter-v1";
9pub const E2B_BUILTIN_TEMPLATE_IDS: [&str; 3] = [
10 E2B_PLATFORM_TEMPLATE_ID,
11 "context-management-moc",
12 "vpc-sandbox",
13];
14pub const MOULIN_PLATFORM_SNAPSHOT_ID: &str = "python";
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
17#[serde(rename_all = "snake_case")]
18pub enum SnapshotVisibility {
19 #[default]
20 Private,
21 Public,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
25#[serde(tag = "kind", rename_all = "snake_case")]
26pub enum SnapshotBuildSpec {
27 Dockerfile {
28 dockerfile: String,
29 },
30 Template {
31 #[serde(alias = "pipPackages")]
32 pip_packages: String,
33 },
34 Git {
35 #[serde(rename = "gitUrl", alias = "git_url")]
36 git_url: String,
37 #[serde(default, rename = "commitSha", alias = "commit_sha")]
38 commit_sha: String,
39 },
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum WorkspaceBuildProvider {
47 Moulin,
48 E2b,
49 Custom(String),
50}
51
52impl WorkspaceBuildProvider {
53 pub fn parse(value: &str) -> Result<Self> {
54 match value {
55 "moulin" => Ok(Self::Moulin),
56 "e2b" => Ok(Self::E2b),
57 other if !other.trim().is_empty() => Ok(Self::Custom(other.into())),
58 _ => Err(anyhow::anyhow!("Workspace build provider is required")),
59 }
60 }
61
62 pub fn as_str(&self) -> &str {
63 match self {
64 Self::Moulin => "moulin",
65 Self::E2b => "e2b",
66 Self::Custom(value) => value,
67 }
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum MoulinImageBuildRecipe {
73 Dockerfile { dockerfile: String },
74 Git { git_url: String, commit_sha: String },
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum E2bImageBuildRecipe {
79 Dockerfile { dockerfile: String },
80 Template { pip_packages: String },
81 Git { git_url: String, commit_sha: String },
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum EnvironmentImageBuildRequest {
88 Moulin(MoulinImageBuildRecipe),
89 E2b(E2bImageBuildRecipe),
90 Custom {
91 provider: String,
92 recipe: SnapshotBuildSpec,
93 },
94}
95
96impl EnvironmentImageBuildRequest {
97 pub fn from_parts(provider: &str, spec: &SnapshotBuildSpec) -> Result<Self> {
98 match (WorkspaceBuildProvider::parse(provider)?, spec) {
99 (WorkspaceBuildProvider::Moulin, SnapshotBuildSpec::Dockerfile { dockerfile }) => {
100 Ok(Self::Moulin(MoulinImageBuildRecipe::Dockerfile {
101 dockerfile: dockerfile.clone(),
102 }))
103 }
104 (
105 WorkspaceBuildProvider::Moulin,
106 SnapshotBuildSpec::Git {
107 git_url,
108 commit_sha,
109 },
110 ) => Ok(Self::Moulin(MoulinImageBuildRecipe::Git {
111 git_url: git_url.clone(),
112 commit_sha: commit_sha.clone(),
113 })),
114 (WorkspaceBuildProvider::E2b, SnapshotBuildSpec::Dockerfile { dockerfile }) => {
115 Ok(Self::E2b(E2bImageBuildRecipe::Dockerfile {
116 dockerfile: dockerfile.clone(),
117 }))
118 }
119 (WorkspaceBuildProvider::E2b, SnapshotBuildSpec::Template { pip_packages }) => {
120 Ok(Self::E2b(E2bImageBuildRecipe::Template {
121 pip_packages: pip_packages.clone(),
122 }))
123 }
124 (
125 WorkspaceBuildProvider::E2b,
126 SnapshotBuildSpec::Git {
127 git_url,
128 commit_sha,
129 },
130 ) => Ok(Self::E2b(E2bImageBuildRecipe::Git {
131 git_url: git_url.clone(),
132 commit_sha: commit_sha.clone(),
133 })),
134 (WorkspaceBuildProvider::Moulin, SnapshotBuildSpec::Template { .. }) => Err(
135 anyhow::anyhow!("Moulin does not support E2B template image recipes"),
136 ),
137 (WorkspaceBuildProvider::Custom(provider), recipe) => Ok(Self::Custom {
138 provider,
139 recipe: recipe.clone(),
140 }),
141 }
142 }
143
144 pub fn provider(&self) -> WorkspaceBuildProvider {
145 match self {
146 Self::Moulin(_) => WorkspaceBuildProvider::Moulin,
147 Self::E2b(_) => WorkspaceBuildProvider::E2b,
148 Self::Custom { provider, .. } => WorkspaceBuildProvider::Custom(provider.clone()),
149 }
150 }
151
152 pub fn provider_name(&self) -> &str {
153 match self {
154 Self::Moulin(_) => "moulin",
155 Self::E2b(_) => "e2b",
156 Self::Custom { provider, .. } => provider,
157 }
158 }
159
160 pub fn snapshot_spec(&self) -> SnapshotBuildSpec {
161 match self {
162 Self::Moulin(MoulinImageBuildRecipe::Dockerfile { dockerfile })
163 | Self::E2b(E2bImageBuildRecipe::Dockerfile { dockerfile }) => {
164 SnapshotBuildSpec::Dockerfile {
165 dockerfile: dockerfile.clone(),
166 }
167 }
168 Self::E2b(E2bImageBuildRecipe::Template { pip_packages }) => {
169 SnapshotBuildSpec::Template {
170 pip_packages: pip_packages.clone(),
171 }
172 }
173 Self::Moulin(MoulinImageBuildRecipe::Git {
174 git_url,
175 commit_sha,
176 })
177 | Self::E2b(E2bImageBuildRecipe::Git {
178 git_url,
179 commit_sha,
180 }) => SnapshotBuildSpec::Git {
181 git_url: git_url.clone(),
182 commit_sha: commit_sha.clone(),
183 },
184 Self::Custom { recipe, .. } => recipe.clone(),
185 }
186 }
187}
188
189pub const SNAPSHOT_DOCKERFILE_MAX_BYTES: usize = 1024 * 1024;
192pub const SNAPSHOT_TEMPLATE_PACKAGES_MAX_BYTES: usize = 64 * 1024;
193
194#[derive(Debug, Clone, Default, PartialEq, Eq)]
195pub struct EnvironmentCatalogConfig {
196 pub default_snapshot: Option<crate::DefaultExecutionSnapshot>,
197 pub default_provider: Option<String>,
198 pub default_resources: SandboxResources,
199 pub default_builds: BTreeMap<String, SnapshotBuildSpec>,
200}
201
202#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(deny_unknown_fields, rename_all = "camelCase")]
204pub struct CreateSnapshotResourceRequest {
205 #[serde(default, skip_serializing_if = "Option::is_none")]
206 pub alias: Option<String>,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub provider: Option<String>,
209 #[serde(default)]
210 pub visibility: SnapshotVisibility,
211 #[serde(default)]
212 pub owners: Vec<OwnerRef>,
213 #[serde(default)]
214 pub builtin: bool,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
216 pub build: Option<SnapshotBuildSpec>,
217}
218
219#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
220#[serde(deny_unknown_fields, rename_all = "camelCase")]
221pub struct BuildBuiltinSnapshotRequest {
222 pub provider: String,
223 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub git_url: Option<String>,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub commit_sha: Option<String>,
227 #[serde(default, skip_serializing_if = "Option::is_none")]
228 pub dockerfile: Option<String>,
229}
230
231pub const ADMIN_BUILD_BUILTIN_SNAPSHOTS_PATH: &str = "/admin/api/workspace-snapshots:build-builtin";
232pub const ADMIN_SNAPSHOTS_PATH: &str = "/admin/api/workspace-snapshots";
233pub const ADMIN_SNAPSHOT_PATH: &str = "/admin/api/workspace-snapshots/{snapshotId}";
234pub const CATALOG_SNAPSHOT_PATH: &str = "/internal/v1/workspace/snapshots:catalog/{snapshotId}";
235
236#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
237#[serde(deny_unknown_fields, rename_all = "camelCase")]
238pub struct ProviderBuildHandle {
239 pub build_id: String,
240 #[serde(default, skip_serializing_if = "Option::is_none")]
241 pub resource_external_id: Option<String>,
242}
243
244#[derive(Debug, Clone, Default, PartialEq, Eq)]
247pub struct EnvironmentBuildObservation {
248 pub stage: Option<String>,
249 pub logs: Vec<String>,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
253pub enum EnvironmentBuildStatus {
254 Building(ProviderBuildHandle),
255 BuildingObserved {
256 handle: ProviderBuildHandle,
257 observation: EnvironmentBuildObservation,
258 },
259 Ready(ProviderResourceRef),
260 ReadyWithMetadata {
261 provider_ref: ProviderResourceRef,
262 digest: Option<String>,
263 size_bytes: Option<u64>,
264 },
265 ReadyObserved {
266 provider_ref: ProviderResourceRef,
267 digest: Option<String>,
268 size_bytes: Option<u64>,
269 observation: EnvironmentBuildObservation,
270 },
271 Failed(String),
272 FailedObserved {
273 error: String,
274 observation: EnvironmentBuildObservation,
275 },
276}
277
278#[async_trait]
280pub trait EnvironmentBuildPort: Send + Sync + Debug {
281 async fn submit_environment_image(
282 &self,
283 request: &EnvironmentImageBuildRequest,
284 idempotency_key: &str,
285 ) -> Result<EnvironmentBuildStatus>;
286
287 async fn poll_environment_image(
288 &self,
289 _provider: WorkspaceBuildProvider,
290 _handle: &ProviderBuildHandle,
291 ) -> Result<EnvironmentBuildStatus> {
292 Err(anyhow::anyhow!(
293 "build provider does not support asynchronous status observation"
294 ))
295 }
296}