@mastra/e2b 0.2.0-alpha.0 → 0.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +7 -1
- package/dist/index.cjs +231 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +231 -1
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/mounts/azure.d.ts +40 -0
- package/dist/sandbox/mounts/azure.d.ts.map +1 -0
- package/dist/sandbox/mounts/index.d.ts +1 -0
- package/dist/sandbox/mounts/index.d.ts.map +1 -1
- package/dist/sandbox/mounts/types.d.ts +3 -2
- package/dist/sandbox/mounts/types.d.ts.map +1 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -195,6 +195,231 @@ async function mountGCS(mountPath, config, ctx) {
|
|
|
195
195
|
throw new Error(`Failed to mount GCS bucket: ${stderr || stdout || error}`);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
+
var SAFE_CONTAINER_NAME = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;
|
|
199
|
+
var BLOBFUSE2_GITHUB_DEB = "https://github.com/Azure/azure-storage-fuse/releases/download/blobfuse2-2.5.1/blobfuse2-2.5.1-Ubuntu-22.04.x86_64.deb";
|
|
200
|
+
function validateContainerName(name) {
|
|
201
|
+
if (!SAFE_CONTAINER_NAME.test(name) || name.includes("--")) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Invalid Azure container name: "${name}". Container names must be 3-63 lowercase alphanumeric characters or hyphens, with no consecutive hyphens.`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function parseConnectionString(cs) {
|
|
208
|
+
const out = {};
|
|
209
|
+
for (const part of cs.split(";")) {
|
|
210
|
+
const eq = part.indexOf("=");
|
|
211
|
+
if (eq === -1) continue;
|
|
212
|
+
const key = part.slice(0, eq).trim();
|
|
213
|
+
const value = part.slice(eq + 1).trim();
|
|
214
|
+
if (!value) continue;
|
|
215
|
+
if (key === "AccountName") out.accountName = value;
|
|
216
|
+
else if (key === "AccountKey") out.accountKey = value;
|
|
217
|
+
else if (key === "SharedAccessSignature") out.sasToken = value;
|
|
218
|
+
else if (key === "BlobEndpoint") out.endpoint = value;
|
|
219
|
+
else if (key === "EndpointSuffix") out.endpointSuffix = value;
|
|
220
|
+
else if (key === "DefaultEndpointsProtocol") out.protocol = value;
|
|
221
|
+
}
|
|
222
|
+
if (!out.endpoint && out.accountName) {
|
|
223
|
+
out.endpoint = `${out.protocol || "https"}://${out.accountName}.blob.${out.endpointSuffix || "core.windows.net"}`;
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
function yamlString(value) {
|
|
228
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
229
|
+
}
|
|
230
|
+
function parseOsRelease(output) {
|
|
231
|
+
const values = {};
|
|
232
|
+
for (const line of output.split("\n")) {
|
|
233
|
+
const eq = line.indexOf("=");
|
|
234
|
+
if (eq === -1) continue;
|
|
235
|
+
const key = line.slice(0, eq);
|
|
236
|
+
const value = line.slice(eq + 1).trim().replace(/^"|"$/g, "");
|
|
237
|
+
values[key] = value;
|
|
238
|
+
}
|
|
239
|
+
return values;
|
|
240
|
+
}
|
|
241
|
+
function resolveMicrosoftAptRepos(osReleaseOutput) {
|
|
242
|
+
const osRelease = parseOsRelease(osReleaseOutput);
|
|
243
|
+
const distroId = osRelease.ID || "ubuntu";
|
|
244
|
+
const codename = osRelease.VERSION_CODENAME || (distroId === "debian" ? "bookworm" : "jammy");
|
|
245
|
+
const versionId = osRelease.VERSION_ID || (distroId === "debian" ? "12" : "22.04");
|
|
246
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(codename)) {
|
|
247
|
+
throw new Error(`Invalid distro codename for blobfuse2 repo: "${codename}"`);
|
|
248
|
+
}
|
|
249
|
+
if (!/^\d+(?:\.\d+)?$/.test(versionId)) {
|
|
250
|
+
throw new Error(`Invalid distro version for blobfuse2 repo: "${versionId}"`);
|
|
251
|
+
}
|
|
252
|
+
if (distroId === "debian") {
|
|
253
|
+
const repos = [
|
|
254
|
+
{ repoUrl: `https://packages.microsoft.com/debian/${versionId.split(".")[0]}/prod`, suite: codename }
|
|
255
|
+
];
|
|
256
|
+
if (versionId.split(".")[0] !== "12" || codename !== "bookworm") {
|
|
257
|
+
repos.push({ repoUrl: "https://packages.microsoft.com/debian/12/prod", suite: "bookworm" });
|
|
258
|
+
}
|
|
259
|
+
return repos;
|
|
260
|
+
}
|
|
261
|
+
if (distroId === "ubuntu") {
|
|
262
|
+
const repos = [{ repoUrl: `https://packages.microsoft.com/ubuntu/${versionId}/prod`, suite: codename }];
|
|
263
|
+
if (versionId !== "24.04" || codename !== "noble") {
|
|
264
|
+
repos.push({ repoUrl: "https://packages.microsoft.com/ubuntu/24.04/prod", suite: "noble" });
|
|
265
|
+
}
|
|
266
|
+
if (versionId !== "22.04" || codename !== "jammy") {
|
|
267
|
+
repos.push({ repoUrl: "https://packages.microsoft.com/ubuntu/22.04/prod", suite: "jammy" });
|
|
268
|
+
}
|
|
269
|
+
return repos;
|
|
270
|
+
}
|
|
271
|
+
throw new Error(`Unsupported distro for blobfuse2 runtime installation: "${distroId}"`);
|
|
272
|
+
}
|
|
273
|
+
function resolveAuth(config) {
|
|
274
|
+
let accountName = config.accountName;
|
|
275
|
+
let accountKey = config.accountKey;
|
|
276
|
+
let sasToken = config.sasToken;
|
|
277
|
+
let endpoint = config.endpoint;
|
|
278
|
+
if (config.connectionString) {
|
|
279
|
+
const parsed = parseConnectionString(config.connectionString);
|
|
280
|
+
accountName = accountName ?? parsed.accountName;
|
|
281
|
+
accountKey = accountKey ?? parsed.accountKey;
|
|
282
|
+
sasToken = sasToken ?? parsed.sasToken;
|
|
283
|
+
endpoint = endpoint ?? parsed.endpoint;
|
|
284
|
+
}
|
|
285
|
+
let mode;
|
|
286
|
+
if (config.useDefaultCredential) {
|
|
287
|
+
mode = "msi";
|
|
288
|
+
} else if (sasToken) {
|
|
289
|
+
mode = "sas";
|
|
290
|
+
} else if (accountKey) {
|
|
291
|
+
mode = "key";
|
|
292
|
+
} else {
|
|
293
|
+
throw new Error(
|
|
294
|
+
"Azure Blob mount requires credentials: provide connectionString, accountKey + accountName, sasToken + accountName, or useDefaultCredential."
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
if (!accountName) {
|
|
298
|
+
throw new Error("Azure Blob mount requires an accountName (either explicitly or via connectionString).");
|
|
299
|
+
}
|
|
300
|
+
if (endpoint) {
|
|
301
|
+
validateEndpoint(endpoint);
|
|
302
|
+
}
|
|
303
|
+
return { mode, accountName, accountKey, sasToken, endpoint };
|
|
304
|
+
}
|
|
305
|
+
function buildBlobfuseConfig(container, auth, cachePath, readOnly) {
|
|
306
|
+
const lines = [
|
|
307
|
+
"allow-other: true",
|
|
308
|
+
"foreground: false",
|
|
309
|
+
`read-only: ${readOnly ? "true" : "false"}`,
|
|
310
|
+
"logging:",
|
|
311
|
+
" type: silent",
|
|
312
|
+
"components:",
|
|
313
|
+
" - libfuse",
|
|
314
|
+
" - file_cache",
|
|
315
|
+
" - attr_cache",
|
|
316
|
+
" - azstorage",
|
|
317
|
+
"libfuse:",
|
|
318
|
+
" attribute-expiration-sec: 240",
|
|
319
|
+
" entry-expiration-sec: 240",
|
|
320
|
+
" negative-entry-expiration-sec: 120",
|
|
321
|
+
"file_cache:",
|
|
322
|
+
` path: ${yamlString(cachePath)}`,
|
|
323
|
+
" timeout-sec: 120",
|
|
324
|
+
"attr_cache:",
|
|
325
|
+
" timeout-sec: 7200",
|
|
326
|
+
"azstorage:",
|
|
327
|
+
` mode: ${auth.mode}`,
|
|
328
|
+
` account-name: ${yamlString(auth.accountName)}`,
|
|
329
|
+
` container: ${yamlString(container)}`
|
|
330
|
+
];
|
|
331
|
+
if (auth.mode === "key" && auth.accountKey) {
|
|
332
|
+
lines.push(` account-key: ${yamlString(auth.accountKey)}`);
|
|
333
|
+
} else if (auth.mode === "sas" && auth.sasToken) {
|
|
334
|
+
lines.push(` sas: ${yamlString(auth.sasToken)}`);
|
|
335
|
+
}
|
|
336
|
+
if (auth.endpoint) {
|
|
337
|
+
lines.push(` endpoint: ${yamlString(auth.endpoint.replace(/\/$/, ""))}`);
|
|
338
|
+
}
|
|
339
|
+
return lines.join("\n") + "\n";
|
|
340
|
+
}
|
|
341
|
+
async function mountAzure(mountPath, config, ctx) {
|
|
342
|
+
const { sandbox, logger } = ctx;
|
|
343
|
+
validateContainerName(config.container);
|
|
344
|
+
const auth = resolveAuth(config);
|
|
345
|
+
const prefix = config.prefix ? validatePrefix(config.prefix) : void 0;
|
|
346
|
+
const checkResult = await sandbox.commands.run('which blobfuse2 || echo "not found"');
|
|
347
|
+
if (checkResult.stdout.includes("not found")) {
|
|
348
|
+
logger.warn(`${LOG_PREFIX} blobfuse2 not found, attempting runtime installation...`);
|
|
349
|
+
logger.info(
|
|
350
|
+
`${LOG_PREFIX} Tip: For faster startup, pre-install blobfuse2 in your sandbox template via createMountableTemplate()`
|
|
351
|
+
);
|
|
352
|
+
const osReleaseResult = await sandbox.commands.run("cat /etc/os-release 2>/dev/null || true");
|
|
353
|
+
const repos = resolveMicrosoftAptRepos(osReleaseResult.stdout);
|
|
354
|
+
const repoSetupResult = await sandbox.commands.run(
|
|
355
|
+
"sudo mkdir -p /etc/apt/keyrings && curl --retry 3 --retry-all-errors --retry-delay 2 -fsSL https://packages.microsoft.com/keys/microsoft.asc -o /tmp/ms-key.asc && sudo gpg --batch --yes --dearmor -o /etc/apt/keyrings/microsoft.gpg /tmp/ms-key.asc",
|
|
356
|
+
{ timeoutMs: 6e4 }
|
|
357
|
+
);
|
|
358
|
+
let installResult;
|
|
359
|
+
if (repoSetupResult.exitCode === 0) {
|
|
360
|
+
for (const { repoUrl, suite } of repos) {
|
|
361
|
+
installResult = await sandbox.commands.run(
|
|
362
|
+
`echo "deb [signed-by=/etc/apt/keyrings/microsoft.gpg] ${repoUrl} ${suite} main" | sudo tee /etc/apt/sources.list.d/microsoft-prod.list && sudo apt-get update 2>&1 && sudo apt-get install -y blobfuse2 fuse3 2>&1`,
|
|
363
|
+
{ timeoutMs: 18e4 }
|
|
364
|
+
);
|
|
365
|
+
if (installResult.exitCode === 0) break;
|
|
366
|
+
logger.warn(`${LOG_PREFIX} blobfuse2 install failed for ${repoUrl} ${suite}, trying fallback if available`);
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
logger.warn(`${LOG_PREFIX} Failed to set up Microsoft apt repository, trying GitHub release fallback`);
|
|
370
|
+
}
|
|
371
|
+
let verifyResult = await sandbox.commands.run("which blobfuse2 && blobfuse2 --version", { timeoutMs: 3e4 });
|
|
372
|
+
if (verifyResult.exitCode !== 0) {
|
|
373
|
+
installResult = await sandbox.commands.run(
|
|
374
|
+
`sudo apt-get update -qq 2>&1 || true && sudo apt-get install -y fuse3 ca-certificates curl 2>&1 && curl -L --retry 3 --retry-all-errors --retry-delay 2 -fSLo /tmp/blobfuse2.deb ${BLOBFUSE2_GITHUB_DEB} && sudo dpkg -i /tmp/blobfuse2.deb 2>&1 && sudo bash -c 'lib=$(find /usr/lib -name "libfuse3.so.3.*" | head -1); [ -z "$lib" ] || ln -sf "$lib" /usr/lib/x86_64-linux-gnu/libfuse3.so.3'`,
|
|
375
|
+
{ timeoutMs: 18e4 }
|
|
376
|
+
);
|
|
377
|
+
verifyResult = await sandbox.commands.run("which blobfuse2 && blobfuse2 --version", { timeoutMs: 3e4 });
|
|
378
|
+
}
|
|
379
|
+
if (!installResult || verifyResult.exitCode !== 0) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`Failed to install blobfuse2. For Azure Blob mounting, your template needs blobfuse2 and fuse3.
|
|
382
|
+
|
|
383
|
+
Option 1: Use createMountableTemplate() helper:
|
|
384
|
+
import { E2BSandbox, createMountableTemplate } from '@mastra/e2b';
|
|
385
|
+
const sandbox = new E2BSandbox({ template: createMountableTemplate() });
|
|
386
|
+
|
|
387
|
+
Option 2: Customize the base template:
|
|
388
|
+
new E2BSandbox({ template: base => base.aptInstall(['your-packages']) })
|
|
389
|
+
|
|
390
|
+
Error details: ${verifyResult.stderr || verifyResult.stdout || installResult?.stderr || installResult?.stdout || "unknown error"}`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const mountHash = createHash("md5").update(mountPath).digest("hex").slice(0, 8);
|
|
395
|
+
const configPath = `/tmp/.blobfuse2-config-${mountHash}.yaml`;
|
|
396
|
+
const cachePath = `/tmp/blobfuse2-cache-${mountHash}`;
|
|
397
|
+
const yaml = buildBlobfuseConfig(config.container, auth, cachePath, !!config.readOnly);
|
|
398
|
+
await sandbox.commands.run(`sudo rm -f ${configPath}`);
|
|
399
|
+
await sandbox.files.write(configPath, yaml);
|
|
400
|
+
await sandbox.commands.run(`sudo chown root:root ${configPath} && sudo chmod 600 ${configPath}`);
|
|
401
|
+
await sandbox.commands.run(`sudo rm -rf ${shellQuote(cachePath)} && sudo mkdir -p ${shellQuote(cachePath)}`);
|
|
402
|
+
const prefixFlags = prefix ? ` --virtual-directory=true --subdirectory=${shellQuote(prefix)}` : "";
|
|
403
|
+
const mountCmd = `sudo blobfuse2 mount ${shellQuote(mountPath)} --config-file=${shellQuote(configPath)}${prefixFlags}`;
|
|
404
|
+
logger.debug(`${LOG_PREFIX} Mounting Azure Blob:`, mountCmd);
|
|
405
|
+
try {
|
|
406
|
+
const result = await sandbox.commands.run(mountCmd, { timeoutMs: 6e4 });
|
|
407
|
+
logger.debug(`${LOG_PREFIX} blobfuse2 result:`, {
|
|
408
|
+
exitCode: result.exitCode,
|
|
409
|
+
stdout: result.stdout,
|
|
410
|
+
stderr: result.stderr
|
|
411
|
+
});
|
|
412
|
+
if (result.exitCode !== 0) {
|
|
413
|
+
throw new Error(`Failed to mount Azure Blob container: ${result.stderr || result.stdout}`);
|
|
414
|
+
}
|
|
415
|
+
} catch (error) {
|
|
416
|
+
const errorObj = error;
|
|
417
|
+
const stderr = errorObj.result?.stderr || "";
|
|
418
|
+
const stdout = errorObj.result?.stdout || "";
|
|
419
|
+
logger.error(`${LOG_PREFIX} blobfuse2 error:`, { stderr, stdout, error: String(error) });
|
|
420
|
+
throw new Error(`Failed to mount Azure Blob container: ${stderr || stdout || error}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
198
423
|
var E2BProcessHandle = class extends ProcessHandle {
|
|
199
424
|
pid;
|
|
200
425
|
_e2bHandle;
|
|
@@ -602,6 +827,11 @@ var E2BSandbox = class extends MastraSandbox {
|
|
|
602
827
|
await mountGCS(mountPath, config, mountCtx);
|
|
603
828
|
this.logger.debug(`${LOG_PREFIX} Mounted GCS bucket at ${mountPath}`);
|
|
604
829
|
break;
|
|
830
|
+
case "azure-blob":
|
|
831
|
+
this.logger.debug(`${LOG_PREFIX} Mounting Azure Blob container at ${mountPath}...`);
|
|
832
|
+
await mountAzure(mountPath, config, mountCtx);
|
|
833
|
+
this.logger.debug(`${LOG_PREFIX} Mounted Azure Blob container at ${mountPath}`);
|
|
834
|
+
break;
|
|
605
835
|
default:
|
|
606
836
|
this.mounts.set(mountPath, {
|
|
607
837
|
filesystem,
|
|
@@ -677,7 +907,7 @@ var E2BSandbox = class extends MastraSandbox {
|
|
|
677
907
|
}
|
|
678
908
|
this.logger.debug(`${LOG_PREFIX} Reconciling mounts. Expected paths:`, expectedMountPaths);
|
|
679
909
|
const mountsResult = await this._sandbox.commands.run(
|
|
680
|
-
`grep -E 'fuse\\.(s3fs|gcsfuse)' /proc/mounts | awk '{print $2}'`
|
|
910
|
+
`grep -E 'fuse\\.(s3fs|gcsfuse|blobfuse2)' /proc/mounts | awk '{print $2}'`
|
|
681
911
|
);
|
|
682
912
|
const currentMounts = mountsResult.stdout.trim().split("\n").filter((p) => p.length > 0);
|
|
683
913
|
this.logger.debug(`${LOG_PREFIX} Current FUSE mounts in sandbox:`, currentMounts);
|