@mastra/e2b 0.10.0 → 0.11.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 +61 -0
- package/LICENSE.md +6 -4
- package/dist/code-mode/transport.d.ts.map +1 -1
- package/dist/index.cjs +516 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +511 -40
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +44 -3
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/process-manager.d.ts.map +1 -1
- package/dist/utils/repo-template.d.ts +151 -0
- package/dist/utils/repo-template.d.ts.map +1 -0
- package/dist/utils/template.d.ts +101 -3
- package/dist/utils/template.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { E2BSandbox, type E2BSandboxOptions } from './sandbox/index.js';
|
|
2
2
|
export { E2BProcessManager } from './sandbox/process-manager.js';
|
|
3
|
-
export { createDefaultMountableTemplate, type TemplateSpec, type MountableTemplateResult } from './utils/template.js';
|
|
3
|
+
export { createDefaultMountableTemplate, isNamedTemplateSpec, isDeferredNamedTemplateSpec, DEFAULT_NODE_VERSION, type TemplateSpec, type NamedTemplateSpec, type DeferredNamedTemplateSpec, type MountableTemplateResult, type MountableTemplateOptions, } from './utils/template.js';
|
|
4
|
+
export { createRepoTemplate, refreshRepoTemplate, repoTemplateRef, type RepoTemplateOptions, type RepoTemplateIdentity, type RepositoryAccess, type RefreshRepoTemplateResult, } from './utils/repo-template.js';
|
|
4
5
|
export { type E2BS3MountConfig, type E2BGCSMountConfig, type E2BAzureBlobMountConfig, type E2BMountConfig, } from './sandbox/mounts/index.js';
|
|
5
6
|
export { e2bSandboxProvider } from './provider.js';
|
|
6
7
|
export { E2BCodeModeTransport } from './code-mode/transport.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,EACnB,2BAA2B,EAC3B,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { MastraSandbox, ProcessHandle, SandboxNotReadyError, SandboxProcessManager, UnsupportedStdinCloseError } from "@mastra/core/workspace";
|
|
2
2
|
import { Sandbox, Template } from "e2b";
|
|
3
3
|
import { createHash, randomBytes } from "crypto";
|
|
4
|
+
import { execFile } from "child_process";
|
|
5
|
+
import { promisify } from "util";
|
|
4
6
|
import { FRAME_PREFIX, buildProgramModule, buildRunner, sanitizeToolId } from "@mastra/core/tools";
|
|
5
7
|
import { transformSync } from "esbuild";
|
|
6
8
|
/**
|
|
9
|
+
* Node.js version installed into the default template — the current LTS at
|
|
10
|
+
* pin time. An exact version rather than an `lts` alias so the template's
|
|
11
|
+
* contents can never drift under a stable identity hash; bump deliberately
|
|
12
|
+
* (each bump builds new templates).
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_NODE_VERSION = "24.20.0";
|
|
15
|
+
const NODE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
16
|
+
function isNamedTemplateSpec(spec) {
|
|
17
|
+
return typeof spec === "object" && spec !== null && "ref" in spec && "template" in spec;
|
|
18
|
+
}
|
|
19
|
+
function isDeferredNamedTemplateSpec(spec) {
|
|
20
|
+
return typeof spec === "object" && spec !== null && "resolveSpec" in spec && typeof spec.resolveSpec === "function";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
7
23
|
* Create a base template with FUSE mounting dependencies pre-installed.
|
|
8
24
|
*
|
|
9
25
|
* This template includes s3fs and fuse packages required for mounting
|
|
@@ -33,17 +49,28 @@ import { transformSync } from "esbuild";
|
|
|
33
49
|
*
|
|
34
50
|
* @returns Object with template builder and deterministic ID
|
|
35
51
|
*/
|
|
36
|
-
function createDefaultMountableTemplate() {
|
|
52
|
+
function createDefaultMountableTemplate(options) {
|
|
37
53
|
const aptPackages = ["s3fs", "fuse"];
|
|
54
|
+
const cpuCount = options?.cpuCount ?? 2;
|
|
55
|
+
const memoryMB = options?.memoryMB ?? 1024;
|
|
56
|
+
const nodeVersion = options?.nodeVersion ?? "24.20.0";
|
|
57
|
+
if (!NODE_VERSION_PATTERN.test(nodeVersion)) throw new Error(`Invalid nodeVersion "${nodeVersion}": expected an exact version like "24.20.0"`);
|
|
38
58
|
const config = {
|
|
39
|
-
version: "
|
|
40
|
-
aptPackages
|
|
59
|
+
version: "v3",
|
|
60
|
+
aptPackages,
|
|
61
|
+
cpuCount,
|
|
62
|
+
memoryMB,
|
|
63
|
+
nodeVersion
|
|
41
64
|
};
|
|
42
65
|
const hash = createHash("sha256").update(JSON.stringify(config, Object.keys(config).sort())).digest("hex").slice(0, 16);
|
|
43
66
|
return {
|
|
44
|
-
template: Template().fromTemplate("base").aptInstall(aptPackages),
|
|
67
|
+
template: Template().fromTemplate("base").aptInstall(aptPackages).runCmd(`curl -fsSL https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-linux-x64.tar.gz | sudo tar -xz -C /usr/local --strip-components=1`).runCmd("sudo corepack enable").runCmd(`echo 'COREPACK_ENABLE_DOWNLOAD_PROMPT=0' | sudo tee -a /etc/environment`).setEnvs({ COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" }),
|
|
45
68
|
id: `mastra-${hash}`,
|
|
46
|
-
aptPackages
|
|
69
|
+
aptPackages,
|
|
70
|
+
resources: {
|
|
71
|
+
cpuCount,
|
|
72
|
+
memoryMB
|
|
73
|
+
}
|
|
47
74
|
};
|
|
48
75
|
}
|
|
49
76
|
//#endregion
|
|
@@ -578,6 +605,13 @@ function validateMountPath(mountPath) {
|
|
|
578
605
|
/** Allowlist for marker filenames from ls output — e.g. "mount-abc123" */
|
|
579
606
|
const SAFE_MARKER_NAME = /^mount-[a-z0-9]+$/;
|
|
580
607
|
/**
|
|
608
|
+
* Per-process dedupe of background template rebuild triggers, keyed by
|
|
609
|
+
* template ref. Retained on successful trigger (the ref only ever needs one
|
|
610
|
+
* build; once it exists the exists-check short-circuits before this path),
|
|
611
|
+
* cleared on trigger failure so a later start can retry.
|
|
612
|
+
*/
|
|
613
|
+
const inFlightBackgroundBuilds = /* @__PURE__ */ new Set();
|
|
614
|
+
/**
|
|
581
615
|
* Simplified E2B sandbox implementation.
|
|
582
616
|
*
|
|
583
617
|
* Features:
|
|
@@ -652,10 +686,22 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
652
686
|
_preferredSandboxId;
|
|
653
687
|
_instructionsOverride;
|
|
654
688
|
_constructorOptions;
|
|
655
|
-
/**
|
|
689
|
+
/**
|
|
690
|
+
* Resolved template ID after building (if needed). The single cache for
|
|
691
|
+
* template resolution: `resolveTemplate()` returns it when set, and the
|
|
692
|
+
* create-time fallback ladder rewrites it to whichever template actually
|
|
693
|
+
* produced a sandbox.
|
|
694
|
+
*
|
|
695
|
+
* `protected` so a subclass with its own default template (e.g. desktop
|
|
696
|
+
* sandboxes) shares the same cache when it overrides `resolveTemplate()`.
|
|
697
|
+
*/
|
|
656
698
|
_resolvedTemplateId;
|
|
657
|
-
/**
|
|
658
|
-
|
|
699
|
+
/**
|
|
700
|
+
* The named spec a deferred template spec resolved to — kept so the
|
|
701
|
+
* 404-on-create fallback ladder can walk the same name/fallback rungs it
|
|
702
|
+
* would for a plain named spec.
|
|
703
|
+
*/
|
|
704
|
+
_resolvedNamedSpec;
|
|
659
705
|
constructor(options = {}) {
|
|
660
706
|
super({
|
|
661
707
|
...options,
|
|
@@ -677,10 +723,6 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
677
723
|
this._preferredSandboxId = options.sandboxId;
|
|
678
724
|
this._instructionsOverride = options.instructions;
|
|
679
725
|
this._constructorOptions = { ...options };
|
|
680
|
-
this._templatePreparePromise = this.resolveTemplate().catch((err) => {
|
|
681
|
-
this.logger.debug(`${LOG_PREFIX} Template preparation error (will retry on start):`, err);
|
|
682
|
-
return "";
|
|
683
|
-
});
|
|
684
726
|
}
|
|
685
727
|
/**
|
|
686
728
|
* Construct a sibling `E2BSandbox` that inherits this sandbox's
|
|
@@ -770,11 +812,7 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
770
812
|
this.logger.debug(`${LOG_PREFIX} Mount reconciliation complete`);
|
|
771
813
|
}
|
|
772
814
|
async create() {
|
|
773
|
-
|
|
774
|
-
if (!resolvedTemplateId) {
|
|
775
|
-
this.logger.debug(`${LOG_PREFIX} Template preparation failed earlier, retrying...`);
|
|
776
|
-
resolvedTemplateId = await this.resolveTemplate();
|
|
777
|
-
}
|
|
815
|
+
const resolvedTemplateId = await this.resolveTemplate();
|
|
778
816
|
this.logger.debug(`${LOG_PREFIX} Creating new sandbox for: ${this.id} with template: ${resolvedTemplateId}`);
|
|
779
817
|
const createOpts = {
|
|
780
818
|
...this.connectionOpts,
|
|
@@ -786,17 +824,51 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
786
824
|
...this.network && { network: this.network },
|
|
787
825
|
timeoutMs: this.timeout
|
|
788
826
|
};
|
|
827
|
+
const createFromTemplate = (templateId) => this.createSdkSandbox(templateId, createOpts);
|
|
828
|
+
const isTemplateUnusable = (error) => String(error).includes("404");
|
|
789
829
|
let sdkSandbox;
|
|
790
830
|
try {
|
|
791
|
-
sdkSandbox = await
|
|
831
|
+
sdkSandbox = await createFromTemplate(resolvedTemplateId);
|
|
792
832
|
} catch (createError) {
|
|
793
|
-
|
|
794
|
-
|
|
833
|
+
if (!isTemplateUnusable(createError)) throw createError;
|
|
834
|
+
const namedSpec = this.templateSpec && isNamedTemplateSpec(this.templateSpec) ? this.templateSpec : this._resolvedNamedSpec;
|
|
835
|
+
if (namedSpec) {
|
|
836
|
+
this.logger.warn(`${LOG_PREFIX} Creating from '${resolvedTemplateId}' failed, retrying on fallback: ${createError}`);
|
|
837
|
+
this._resolvedTemplateId = void 0;
|
|
838
|
+
const spec = namedSpec;
|
|
839
|
+
const fallbackId = resolvedTemplateId === spec.ref ? await this.resolveFallbackTemplate(spec.fallbackTemplate) : await this.buildOrReuseDefaultTemplate();
|
|
840
|
+
try {
|
|
841
|
+
sdkSandbox = await createFromTemplate(fallbackId);
|
|
842
|
+
this._resolvedTemplateId = fallbackId;
|
|
843
|
+
} catch (fallbackError) {
|
|
844
|
+
if (!isTemplateUnusable(fallbackError)) throw fallbackError;
|
|
845
|
+
const rebuildDefaultAndCreate = async () => {
|
|
846
|
+
this.logger.warn(`${LOG_PREFIX} Default template broken too, rebuilding: ${fallbackError}`);
|
|
847
|
+
const rebuiltId = await this.buildDefaultTemplate();
|
|
848
|
+
const rebuilt = await createFromTemplate(rebuiltId);
|
|
849
|
+
this._resolvedTemplateId = rebuiltId;
|
|
850
|
+
return rebuilt;
|
|
851
|
+
};
|
|
852
|
+
const defaultId = await this.buildOrReuseDefaultTemplate();
|
|
853
|
+
if (defaultId === fallbackId) sdkSandbox = await rebuildDefaultAndCreate();
|
|
854
|
+
else {
|
|
855
|
+
this.logger.warn(`${LOG_PREFIX} Fallback '${fallbackId}' failed too, using default: ${fallbackError}`);
|
|
856
|
+
try {
|
|
857
|
+
sdkSandbox = await createFromTemplate(defaultId);
|
|
858
|
+
this._resolvedTemplateId = defaultId;
|
|
859
|
+
} catch (defaultError) {
|
|
860
|
+
if (!isTemplateUnusable(defaultError)) throw defaultError;
|
|
861
|
+
sdkSandbox = await rebuildDefaultAndCreate();
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
this.logger.debug(`${LOG_PREFIX} Created sandbox ${sdkSandbox.sandboxId} from fallback for: ${this.id}`);
|
|
866
|
+
} else if (!this.templateSpec) {
|
|
795
867
|
this.logger.debug(`${LOG_PREFIX} Template not found, rebuilding: ${resolvedTemplateId}`);
|
|
796
868
|
this._resolvedTemplateId = void 0;
|
|
797
869
|
const rebuiltTemplateId = await this.buildDefaultTemplate();
|
|
798
870
|
this.logger.debug(`${LOG_PREFIX} Retrying sandbox creation with rebuilt template: ${rebuiltTemplateId}`);
|
|
799
|
-
sdkSandbox = await
|
|
871
|
+
sdkSandbox = await createFromTemplate(rebuiltTemplateId);
|
|
800
872
|
} else throw createError;
|
|
801
873
|
}
|
|
802
874
|
this._sandbox = sdkSandbox;
|
|
@@ -1236,31 +1308,57 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
1236
1308
|
*/
|
|
1237
1309
|
async resolveTemplate() {
|
|
1238
1310
|
if (this._resolvedTemplateId) return this._resolvedTemplateId;
|
|
1239
|
-
if (!this.templateSpec)
|
|
1240
|
-
const { template, id } = createDefaultMountableTemplate();
|
|
1241
|
-
if (await Template.exists(id, this.connectionOpts)) {
|
|
1242
|
-
this.logger.debug(`${LOG_PREFIX} Using cached mountable template: ${id}`);
|
|
1243
|
-
this._resolvedTemplateId = id;
|
|
1244
|
-
return id;
|
|
1245
|
-
}
|
|
1246
|
-
this.logger.debug(`${LOG_PREFIX} Building default mountable template: ${id}...`);
|
|
1247
|
-
const buildResult = await Template.build(template, id, this.connectionOpts);
|
|
1248
|
-
this._resolvedTemplateId = buildResult.templateId;
|
|
1249
|
-
this.logger.debug(`${LOG_PREFIX} Template built and cached: ${buildResult.templateId}`);
|
|
1250
|
-
return buildResult.templateId;
|
|
1251
|
-
}
|
|
1311
|
+
if (!this.templateSpec) return await this.buildOrReuseDefaultTemplate();
|
|
1252
1312
|
if (typeof this.templateSpec === "string") {
|
|
1253
1313
|
this._resolvedTemplateId = this.templateSpec;
|
|
1254
1314
|
return this.templateSpec;
|
|
1255
1315
|
}
|
|
1316
|
+
let spec;
|
|
1317
|
+
if (isDeferredNamedTemplateSpec(this.templateSpec)) try {
|
|
1318
|
+
spec = await this.templateSpec.resolveSpec();
|
|
1319
|
+
this._resolvedNamedSpec = spec;
|
|
1320
|
+
} catch (error) {
|
|
1321
|
+
this.logger.warn(`${LOG_PREFIX} Deferred template spec resolution failed, falling back: ${error}`);
|
|
1322
|
+
return await this.resolveFallbackTemplate(void 0);
|
|
1323
|
+
}
|
|
1324
|
+
else spec = this.templateSpec;
|
|
1325
|
+
if (isNamedTemplateSpec(spec)) {
|
|
1326
|
+
const { ref, template: namedTemplate, fallbackTemplate, staleRef, buildTags, buildResources } = spec;
|
|
1327
|
+
const buildOpts = {
|
|
1328
|
+
...this.connectionOpts,
|
|
1329
|
+
...buildTags?.length ? { tags: buildTags } : {},
|
|
1330
|
+
...buildResources
|
|
1331
|
+
};
|
|
1332
|
+
try {
|
|
1333
|
+
if (await Template.exists(ref, this.connectionOpts)) {
|
|
1334
|
+
this.logger.debug(`${LOG_PREFIX} Using cached template: ${ref}`);
|
|
1335
|
+
this._resolvedTemplateId = ref;
|
|
1336
|
+
return ref;
|
|
1337
|
+
}
|
|
1338
|
+
if (staleRef && staleRef !== ref && await Template.exists(staleRef, this.connectionOpts)) {
|
|
1339
|
+
this.logger.debug(`${LOG_PREFIX} Using stale build ${staleRef}; rebuilding ${ref} in background`);
|
|
1340
|
+
this.triggerBackgroundBuild(namedTemplate, ref, buildOpts);
|
|
1341
|
+
this._resolvedTemplateId = staleRef;
|
|
1342
|
+
return staleRef;
|
|
1343
|
+
}
|
|
1344
|
+
this.logger.debug(`${LOG_PREFIX} Building template: ${ref}...`);
|
|
1345
|
+
const buildResult = await Template.build(namedTemplate, ref, buildOpts);
|
|
1346
|
+
this.logger.debug(`${LOG_PREFIX} Template built: ${buildResult.templateId}`);
|
|
1347
|
+
this._resolvedTemplateId = ref;
|
|
1348
|
+
return ref;
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
this.logger.warn(`${LOG_PREFIX} Template '${ref}' resolution failed, falling back: ${error}`);
|
|
1351
|
+
return await this.resolveFallbackTemplate(fallbackTemplate);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1256
1354
|
let template;
|
|
1257
1355
|
let templateName;
|
|
1258
|
-
if (typeof
|
|
1356
|
+
if (typeof spec === "function") {
|
|
1259
1357
|
const { template: baseTemplate } = createDefaultMountableTemplate();
|
|
1260
|
-
template =
|
|
1358
|
+
template = spec(baseTemplate);
|
|
1261
1359
|
templateName = `mastra-custom-${this.id.replace(/[^a-zA-Z0-9-]/g, "-")}`;
|
|
1262
1360
|
} else {
|
|
1263
|
-
template =
|
|
1361
|
+
template = spec;
|
|
1264
1362
|
templateName = `mastra-${this.id.replace(/[^a-zA-Z0-9-]/g, "-")}`;
|
|
1265
1363
|
}
|
|
1266
1364
|
this.logger.debug(`${LOG_PREFIX} Building custom template: ${templateName}...`);
|
|
@@ -1270,15 +1368,102 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
1270
1368
|
return buildResult.templateId;
|
|
1271
1369
|
}
|
|
1272
1370
|
/**
|
|
1371
|
+
* Resolve the default mountable template: reuse when it exists, build once
|
|
1372
|
+
* when it does not.
|
|
1373
|
+
*/
|
|
1374
|
+
/**
|
|
1375
|
+
* Resolve a named spec's fallback template. A named fallback gets its own
|
|
1376
|
+
* exists-then-build resolution; anything failing past that (including
|
|
1377
|
+
* specs without a fallback, e.g. repo templates) lands on the default
|
|
1378
|
+
* mountable template so a broken build never wedges a session.
|
|
1379
|
+
*/
|
|
1380
|
+
/**
|
|
1381
|
+
* Trigger a non-blocking template rebuild via `Template.buildInBackground`
|
|
1382
|
+
* (the build runs on E2B's side, so it outlives this process). Deduped
|
|
1383
|
+
* per-process by ref so concurrent session starts on the same moved head
|
|
1384
|
+
* don't stack duplicate builds; a failed TRIGGER clears the guard so a
|
|
1385
|
+
* later start retries. A build that fails server-side simply never
|
|
1386
|
+
* registers the ref — the next start falls back to the stale build again
|
|
1387
|
+
* and re-triggers.
|
|
1388
|
+
*/
|
|
1389
|
+
triggerBackgroundBuild(template, ref, buildOpts) {
|
|
1390
|
+
if (inFlightBackgroundBuilds.has(ref)) return;
|
|
1391
|
+
inFlightBackgroundBuilds.add(ref);
|
|
1392
|
+
Template.buildInBackground(template, ref, buildOpts).then((result) => {
|
|
1393
|
+
this.logger.debug(`${LOG_PREFIX} Background template build triggered: ${ref} (${result.buildId})`);
|
|
1394
|
+
}).catch((error) => {
|
|
1395
|
+
inFlightBackgroundBuilds.delete(ref);
|
|
1396
|
+
this.logger.warn(`${LOG_PREFIX} Background template build trigger failed for '${ref}': ${error}`);
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
async resolveFallbackTemplate(fallbackTemplate) {
|
|
1400
|
+
if (typeof fallbackTemplate === "string") {
|
|
1401
|
+
this._resolvedTemplateId = fallbackTemplate;
|
|
1402
|
+
return fallbackTemplate;
|
|
1403
|
+
}
|
|
1404
|
+
if (fallbackTemplate && isNamedTemplateSpec(fallbackTemplate)) try {
|
|
1405
|
+
if (await Template.exists(fallbackTemplate.ref, this.connectionOpts)) {
|
|
1406
|
+
this._resolvedTemplateId = fallbackTemplate.ref;
|
|
1407
|
+
return fallbackTemplate.ref;
|
|
1408
|
+
}
|
|
1409
|
+
const buildResult = await Template.build(fallbackTemplate.template, fallbackTemplate.ref, {
|
|
1410
|
+
...this.connectionOpts,
|
|
1411
|
+
...fallbackTemplate.buildResources
|
|
1412
|
+
});
|
|
1413
|
+
this._resolvedTemplateId = buildResult.templateId;
|
|
1414
|
+
return buildResult.templateId;
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
this.logger.warn(`${LOG_PREFIX} Fallback template '${fallbackTemplate.ref}' failed too: ${error}`);
|
|
1417
|
+
return await this.buildOrReuseDefaultTemplate();
|
|
1418
|
+
}
|
|
1419
|
+
if (fallbackTemplate) try {
|
|
1420
|
+
const buildResult = await Template.build(fallbackTemplate, `mastra-fallback-${this.id.replace(/[^a-zA-Z0-9-]/g, "-")}`, this.connectionOpts);
|
|
1421
|
+
this._resolvedTemplateId = buildResult.templateId;
|
|
1422
|
+
return buildResult.templateId;
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
this.logger.warn(`${LOG_PREFIX} Fallback template build failed, using default: ${error}`);
|
|
1425
|
+
return await this.buildOrReuseDefaultTemplate();
|
|
1426
|
+
}
|
|
1427
|
+
return await this.buildOrReuseDefaultTemplate();
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Resources the configured template asked for. The default mountable
|
|
1431
|
+
* template honors them too, so a repo template that falls back never
|
|
1432
|
+
* silently downgrades the machine — a 2 GB session's setup would OOM in
|
|
1433
|
+
* the 1 GB default. Per-size default templates cost one extra build each.
|
|
1434
|
+
*/
|
|
1435
|
+
requestedBuildResources() {
|
|
1436
|
+
return (this.templateSpec && isNamedTemplateSpec(this.templateSpec) ? this.templateSpec : this._resolvedNamedSpec)?.buildResources;
|
|
1437
|
+
}
|
|
1438
|
+
async buildOrReuseDefaultTemplate() {
|
|
1439
|
+
const { template, id, resources } = createDefaultMountableTemplate(this.requestedBuildResources());
|
|
1440
|
+
if (await Template.exists(id, this.connectionOpts)) {
|
|
1441
|
+
this.logger.debug(`${LOG_PREFIX} Using cached mountable template: ${id}`);
|
|
1442
|
+
this._resolvedTemplateId = id;
|
|
1443
|
+
return id;
|
|
1444
|
+
}
|
|
1445
|
+
this.logger.debug(`${LOG_PREFIX} Building default mountable template: ${id}...`);
|
|
1446
|
+
const buildResult = await Template.build(template, id, {
|
|
1447
|
+
...this.connectionOpts,
|
|
1448
|
+
...resources
|
|
1449
|
+
});
|
|
1450
|
+
this._resolvedTemplateId = buildResult.templateId;
|
|
1451
|
+
this.logger.debug(`${LOG_PREFIX} Template built and cached: ${buildResult.templateId}`);
|
|
1452
|
+
return buildResult.templateId;
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1273
1455
|
* Build the default mountable template (bypasses exists check).
|
|
1274
1456
|
*
|
|
1275
1457
|
* Override point: called from the template-not-found retry path in
|
|
1276
1458
|
* `start()` when no explicit template was configured.
|
|
1277
1459
|
*/
|
|
1278
1460
|
async buildDefaultTemplate() {
|
|
1279
|
-
const { template, id } = createDefaultMountableTemplate();
|
|
1461
|
+
const { template, id, resources } = createDefaultMountableTemplate(this.requestedBuildResources());
|
|
1280
1462
|
this.logger.debug(`${LOG_PREFIX} Building default mountable template: ${id}...`);
|
|
1281
|
-
const buildResult = await Template.build(template, id,
|
|
1463
|
+
const buildResult = await Template.build(template, id, {
|
|
1464
|
+
...this.connectionOpts,
|
|
1465
|
+
...resources
|
|
1466
|
+
});
|
|
1282
1467
|
this._resolvedTemplateId = buildResult.templateId;
|
|
1283
1468
|
this.logger.debug(`${LOG_PREFIX} Template built: ${buildResult.templateId}`);
|
|
1284
1469
|
return buildResult.templateId;
|
|
@@ -1368,6 +1553,292 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
|
|
|
1368
1553
|
}
|
|
1369
1554
|
};
|
|
1370
1555
|
//#endregion
|
|
1556
|
+
//#region src/utils/repo-template.ts
|
|
1557
|
+
/**
|
|
1558
|
+
* Sha-tagged repo templates.
|
|
1559
|
+
*
|
|
1560
|
+
* A repo template is an E2B template with the repository already cloned and
|
|
1561
|
+
* its dependencies installed at a known commit. Sessions started from it only
|
|
1562
|
+
* need `git fetch` + checkout of their actual ref plus setup drift, instead
|
|
1563
|
+
* of a cold clone + full install.
|
|
1564
|
+
*
|
|
1565
|
+
* There is exactly ONE template per (repo, setup command, workdir): the
|
|
1566
|
+
* template name is a deterministic `mastra-repo-<hash>` over those inputs,
|
|
1567
|
+
* and the commit sha rides as a docker-style TAG on that name
|
|
1568
|
+
* (`mastra-repo-<hash>:sha-<sha>`). A moved default branch produces a new
|
|
1569
|
+
* tag via a rebuild-in-place of the same template — old sha tags remain as
|
|
1570
|
+
* prunable build history instead of accumulating stale template names.
|
|
1571
|
+
* Builds are lazy: the first `E2BSandbox.start()` that resolves a missing
|
|
1572
|
+
* tag triggers the build; nothing pre-builds templates for idle repos.
|
|
1573
|
+
*
|
|
1574
|
+
* Credential invariant: a build credential may enter the template
|
|
1575
|
+
* DEFINITION (via `setEnvs`, visible to build steps but not persisted into
|
|
1576
|
+
* runtime sandbox environments) and the build process — never the image
|
|
1577
|
+
* filesystem. Clones authenticate through an in-shell computed
|
|
1578
|
+
* `http.extraheader`, so no tokened remote URL or credential file can land
|
|
1579
|
+
* in a captured layer. Callers must supply a short-lived credential (a
|
|
1580
|
+
* GitHub App installation token, which self-expires); never a long-lived
|
|
1581
|
+
* PAT. Without a credential the clone is plain tokenless HTTPS — public
|
|
1582
|
+
* repos build fine; a private repo's build fails and the sandbox falls back
|
|
1583
|
+
* to the fallback template, with the session's runtime setup performing the
|
|
1584
|
+
* full clone using its runtime-injected credential instead.
|
|
1585
|
+
*/
|
|
1586
|
+
const execFileAsync = promisify(execFile);
|
|
1587
|
+
const ALIAS_VERSION = "v5";
|
|
1588
|
+
/**
|
|
1589
|
+
* Stable tag assigned to every successful repo-template build. Points at the
|
|
1590
|
+
* latest build regardless of sha, so a moved head can boot from the previous
|
|
1591
|
+
* build (`name:current`) while the fresh sha builds in the background.
|
|
1592
|
+
*/
|
|
1593
|
+
const CURRENT_TAG = "current";
|
|
1594
|
+
/**
|
|
1595
|
+
* Env var carrying the repository credential during the build. The same
|
|
1596
|
+
* name a session installs before running setup, so a setup command sees the
|
|
1597
|
+
* same environment in both places. Set via `setEnvs`; the git auth header is
|
|
1598
|
+
* computed from it too.
|
|
1599
|
+
*/
|
|
1600
|
+
const BUILD_TOKEN_ENV = "GH_TOKEN";
|
|
1601
|
+
/**
|
|
1602
|
+
* Clone URLs interpolate into build shell commands, so constrain them to
|
|
1603
|
+
* https plus plain host/path characters. This rejects shell metacharacters
|
|
1604
|
+
* outright rather than escaping them. Every regex here is a single anchored
|
|
1605
|
+
* character class, so matching stays linear on adversarial input; the
|
|
1606
|
+
* structural checks (scheme, host, path segments) go through WHATWG URL
|
|
1607
|
+
* parsing instead of one big backtracking pattern.
|
|
1608
|
+
*/
|
|
1609
|
+
const CLONE_URL_ALLOWED_CHARS = /^[a-z0-9:/._-]+$/i;
|
|
1610
|
+
const CLONE_URL_HOST_PATTERN = /^[a-z0-9.-]+$/i;
|
|
1611
|
+
const CLONE_URL_SEGMENT_PATTERN = /^[\w.-]+$/;
|
|
1612
|
+
const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
|
|
1613
|
+
function isValidCloneUrl(cloneUrl) {
|
|
1614
|
+
if (cloneUrl.length > 2048 || !CLONE_URL_ALLOWED_CHARS.test(cloneUrl)) return false;
|
|
1615
|
+
let url;
|
|
1616
|
+
try {
|
|
1617
|
+
url = new URL(cloneUrl);
|
|
1618
|
+
} catch {
|
|
1619
|
+
return false;
|
|
1620
|
+
}
|
|
1621
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) return false;
|
|
1622
|
+
if (!CLONE_URL_HOST_PATTERN.test(url.hostname)) return false;
|
|
1623
|
+
const segments = url.pathname.split("/").slice(1);
|
|
1624
|
+
return segments.length > 0 && segments.every((segment) => CLONE_URL_SEGMENT_PATTERN.test(segment));
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Compute the deterministic template ref for a set of repo template inputs
|
|
1628
|
+
* without constructing the builder: `mastra-repo-<hash>` named over
|
|
1629
|
+
* (clone URL, setup command, build env), tag-qualified with `:sha-<sha>`
|
|
1630
|
+
* when the sha is known. Exposed so callers (and proofs) can predict which
|
|
1631
|
+
* ref a sandbox will resolve.
|
|
1632
|
+
*/
|
|
1633
|
+
function repoTemplateRef(identity) {
|
|
1634
|
+
const name = repoTemplateName(identity);
|
|
1635
|
+
return identity.sha ? `${name}:${shaTag(identity.sha)}` : `${name}:${CURRENT_TAG}`;
|
|
1636
|
+
}
|
|
1637
|
+
function repoTemplateName(identity) {
|
|
1638
|
+
const cloneUrl = normalizeCloneUrl(identity.cloneUrl);
|
|
1639
|
+
const config = [
|
|
1640
|
+
ALIAS_VERSION,
|
|
1641
|
+
cloneUrl,
|
|
1642
|
+
identity.setupCommand ?? null,
|
|
1643
|
+
identity.buildEnv ? Object.entries(identity.buildEnv).sort(([a], [b]) => a.localeCompare(b)) : null,
|
|
1644
|
+
identity.cpuCount ?? 2,
|
|
1645
|
+
identity.memoryMB ?? 1024
|
|
1646
|
+
];
|
|
1647
|
+
const hash = createHash("sha256").update(JSON.stringify(config)).digest("hex").slice(0, 8);
|
|
1648
|
+
const { owner, repo } = parseCloneUrl(cloneUrl);
|
|
1649
|
+
return `mastra-repo-${[owner, repo].map((part) => (part ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-/, "").replace(/-$/, "").slice(0, 24)).filter(Boolean).join("-")}-${hash}`;
|
|
1650
|
+
}
|
|
1651
|
+
function shaTag(sha) {
|
|
1652
|
+
return `sha-${sha.slice(0, 12).toLowerCase()}`;
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Create a sha-tagged repo template spec for `E2BSandbox`.
|
|
1656
|
+
*
|
|
1657
|
+
* Returns undefined when {@link RepoTemplateOptions.getRepositoryAccess} is
|
|
1658
|
+
* absent, which is how a session with no repository asks for no template —
|
|
1659
|
+
* so a host can write `template: createRepoTemplate(ctx)` without a
|
|
1660
|
+
* conditional.
|
|
1661
|
+
*
|
|
1662
|
+
* Resolution is deferred: right before the exists-then-build check it
|
|
1663
|
+
* resolves the clone URL and credential, resolves the repository's current
|
|
1664
|
+
* default-branch head (`git ls-remote`, ~100ms, no clone), and keys the
|
|
1665
|
+
* template ref as `mastra-repo-<hash>:sha-<head>` — so a moved default
|
|
1666
|
+
* branch produces a fresh tagged build of the SAME template on the next new
|
|
1667
|
+
* session (rebuild-in-place), and an unmoved head reuses the existing
|
|
1668
|
+
* tagged build. When the head cannot be resolved the ref degrades to the
|
|
1669
|
+
* untagged name and the build clones whatever the default branch is at
|
|
1670
|
+
* build time.
|
|
1671
|
+
*
|
|
1672
|
+
* When the build itself fails — inaccessible repo, registry flake — the
|
|
1673
|
+
* sandbox falls back to its fallback template and the session's runtime
|
|
1674
|
+
* setup performs the full clone, so a broken build never wedges a session.
|
|
1675
|
+
*/
|
|
1676
|
+
function createRepoTemplate(options) {
|
|
1677
|
+
if (!options.getRepositoryAccess) return void 0;
|
|
1678
|
+
return { resolveSpec: async () => (await resolveSpecAtHead(options)).spec };
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Resolve the clone URL and credential, resolve the current default-branch
|
|
1682
|
+
* head, and produce the concrete sha-tagged spec. Shared by the deferred
|
|
1683
|
+
* spec form and {@link refreshRepoTemplate}.
|
|
1684
|
+
*
|
|
1685
|
+
* A failed access call leaves no clone URL and throws, which the sandbox
|
|
1686
|
+
* turns into its default-template fallback rather than a failed start.
|
|
1687
|
+
*/
|
|
1688
|
+
async function resolveSpecAtHead(options) {
|
|
1689
|
+
const access = options.getRepositoryAccess ? await options.getRepositoryAccess().catch(() => void 0) : void 0;
|
|
1690
|
+
const cloneUrl = access?.cloneUrl;
|
|
1691
|
+
if (!cloneUrl) throw new Error("Repo template has no clone URL: repository access returned none.");
|
|
1692
|
+
assertCloneUrl(cloneUrl);
|
|
1693
|
+
const token = access?.authorization?.token;
|
|
1694
|
+
const buildEnv = typeof options.buildEnv === "function" ? await options.buildEnv() : options.buildEnv;
|
|
1695
|
+
const resolved = await resolveDefaultBranchHead(cloneUrl, token).catch(() => void 0);
|
|
1696
|
+
const sha = resolved && SHA_PATTERN.test(resolved) ? resolved : void 0;
|
|
1697
|
+
return {
|
|
1698
|
+
spec: buildRepoTemplateSpec({
|
|
1699
|
+
cloneUrl,
|
|
1700
|
+
...sha ? { sha } : {},
|
|
1701
|
+
...options.setupCommand ? { setupCommand: options.setupCommand } : {},
|
|
1702
|
+
...buildEnv ? { buildEnv } : {},
|
|
1703
|
+
...options.cpuCount !== void 0 ? { cpuCount: options.cpuCount } : {},
|
|
1704
|
+
...options.memoryMB !== void 0 ? { memoryMB: options.memoryMB } : {}
|
|
1705
|
+
}, token),
|
|
1706
|
+
...sha ? { sha } : {}
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Ensure the repo template is built at the repository's current
|
|
1711
|
+
* default-branch head, building it (and moving the `current` tag) when it
|
|
1712
|
+
* is not. This is the same resolution the lazy sandbox-start path performs
|
|
1713
|
+
* — exposed standalone so template warming can be driven externally: call
|
|
1714
|
+
* it from a scheduled workflow (cron) or a merge-to-main event handler and
|
|
1715
|
+
* the next session boots warm instead of paying the build.
|
|
1716
|
+
*
|
|
1717
|
+
* The build is awaited; a build failure rejects so callers can observe it.
|
|
1718
|
+
* An unresolvable head degrades to the sha-less `name:current` form, same
|
|
1719
|
+
* as the lazy path.
|
|
1720
|
+
*/
|
|
1721
|
+
async function refreshRepoTemplate(options, connection) {
|
|
1722
|
+
const { spec, sha } = await resolveSpecAtHead(options);
|
|
1723
|
+
const shaField = sha ? { sha } : {};
|
|
1724
|
+
if (await Template.exists(spec.ref, connection)) return {
|
|
1725
|
+
ref: spec.ref,
|
|
1726
|
+
action: "reused",
|
|
1727
|
+
...shaField
|
|
1728
|
+
};
|
|
1729
|
+
await Template.build(spec.template, spec.ref, {
|
|
1730
|
+
...connection,
|
|
1731
|
+
...spec.buildTags?.length ? { tags: spec.buildTags } : {},
|
|
1732
|
+
...spec.buildResources
|
|
1733
|
+
});
|
|
1734
|
+
return {
|
|
1735
|
+
ref: spec.ref,
|
|
1736
|
+
action: "built",
|
|
1737
|
+
...shaField
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* The clone URL is the only untrusted input that reaches a build command,
|
|
1742
|
+
* so it is checked before it can be interpolated into one. The workdir is
|
|
1743
|
+
* derived from it rather than supplied, so it needs no separate guard.
|
|
1744
|
+
*/
|
|
1745
|
+
function assertCloneUrl(cloneUrl) {
|
|
1746
|
+
if (!isValidCloneUrl(cloneUrl)) throw new Error(`Invalid cloneUrl '${cloneUrl}': expected an https URL with a plain host and path`);
|
|
1747
|
+
if (parseCloneUrl(cloneUrl).repo === "") throw new Error(`Invalid cloneUrl '${cloneUrl}': expected a repository path such as https://host/owner/repo.git`);
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* In-shell git auth flag: computes a basic-auth header from the build env
|
|
1751
|
+
* var at execution time. The stored command contains only the env-var
|
|
1752
|
+
* REFERENCE — the token value never appears in the command string, and no
|
|
1753
|
+
* credential is written to the build filesystem.
|
|
1754
|
+
*/
|
|
1755
|
+
function gitAuthFlag() {
|
|
1756
|
+
return `-c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$${BUILD_TOKEN_ENV}" | base64 -w0)"`;
|
|
1757
|
+
}
|
|
1758
|
+
function buildRepoTemplateSpec(identity, token) {
|
|
1759
|
+
const { sha, setupCommand, buildEnv } = identity;
|
|
1760
|
+
const cloneUrl = normalizeCloneUrl(identity.cloneUrl);
|
|
1761
|
+
const workdir = defaultWorkdir(cloneUrl);
|
|
1762
|
+
const auth = token ? `${gitAuthFlag()} ` : "";
|
|
1763
|
+
const steps = [`git ${auth}clone ${cloneUrl} "${workdir}"`];
|
|
1764
|
+
if (sha) steps.push(`git -C "${workdir}" ${auth}fetch origin ${sha}`, `git -C "${workdir}" checkout ${sha}`);
|
|
1765
|
+
if (setupCommand) steps.push(`cd "${workdir}" && ${setupCommand}`);
|
|
1766
|
+
let template = createDefaultMountableTemplate().template;
|
|
1767
|
+
const env = { ...buildEnv };
|
|
1768
|
+
if (token) env[BUILD_TOKEN_ENV] = token;
|
|
1769
|
+
if (Object.keys(env).length > 0) template = template.setEnvs(env);
|
|
1770
|
+
template = template.runCmd(steps);
|
|
1771
|
+
return {
|
|
1772
|
+
ref: repoTemplateRef(identity),
|
|
1773
|
+
template,
|
|
1774
|
+
staleRef: `${repoTemplateName(identity)}:${CURRENT_TAG}`,
|
|
1775
|
+
buildTags: [CURRENT_TAG],
|
|
1776
|
+
buildResources: {
|
|
1777
|
+
cpuCount: identity.cpuCount ?? 2,
|
|
1778
|
+
memoryMB: identity.memoryMB ?? 1024
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Resolve the repository's current default-branch head over HTTPS
|
|
1784
|
+
* (`git ls-remote <url> HEAD` — no clone; authenticated via an in-process
|
|
1785
|
+
* `http.extraheader` when a token is provided). Returns undefined when the
|
|
1786
|
+
* head cannot be resolved (inaccessible repo, offline, no git binary);
|
|
1787
|
+
* callers degrade to the untagged template ref.
|
|
1788
|
+
*/
|
|
1789
|
+
async function resolveDefaultBranchHead(cloneUrl, token) {
|
|
1790
|
+
try {
|
|
1791
|
+
const authArgs = token ? ["-c", `http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`] : [];
|
|
1792
|
+
const { stdout } = await execFileAsync("git", [
|
|
1793
|
+
...authArgs,
|
|
1794
|
+
"ls-remote",
|
|
1795
|
+
"--",
|
|
1796
|
+
cloneUrl,
|
|
1797
|
+
"HEAD"
|
|
1798
|
+
], {
|
|
1799
|
+
timeout: 1e4,
|
|
1800
|
+
env: {
|
|
1801
|
+
...process.env,
|
|
1802
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
const sha = stdout.split(/\s/, 1)[0];
|
|
1806
|
+
return sha && SHA_PATTERN.test(sha) ? sha : void 0;
|
|
1807
|
+
} catch {
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Canonical form used for identity and for the build's clone: lowercase
|
|
1813
|
+
* host, no trailing `.git` or slash. Two spellings of one repository must
|
|
1814
|
+
* not produce two templates.
|
|
1815
|
+
*/
|
|
1816
|
+
function normalizeCloneUrl(cloneUrl) {
|
|
1817
|
+
let end = cloneUrl.length;
|
|
1818
|
+
while (end > 0 && cloneUrl[end - 1] === "/") end--;
|
|
1819
|
+
return cloneUrl.slice(0, end).replace(/\.git$/i, "").replace(/^(https:\/\/)([^/]+)/i, (_match, scheme, host) => {
|
|
1820
|
+
return `${scheme.toLowerCase()}${host.toLowerCase()}`;
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Split a normalized clone URL into its host and trailing owner/repo pair.
|
|
1825
|
+
* Hosts that nest groups (GitLab subgroups) keep only the last two path
|
|
1826
|
+
* segments as owner/repo; the full URL still drives identity.
|
|
1827
|
+
*/
|
|
1828
|
+
function parseCloneUrl(cloneUrl) {
|
|
1829
|
+
const [host = "", ...segments] = normalizeCloneUrl(cloneUrl).replace(/^https:\/\//i, "").split("/");
|
|
1830
|
+
const repo = segments.at(-1) ?? "";
|
|
1831
|
+
return {
|
|
1832
|
+
host,
|
|
1833
|
+
owner: segments.length > 1 ? segments.at(-2) ?? "" : "",
|
|
1834
|
+
repo
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
function defaultWorkdir(cloneUrl) {
|
|
1838
|
+
const { repo } = parseCloneUrl(cloneUrl);
|
|
1839
|
+
return `$HOME/${repo.replace(/[^\w.-]/g, "-").replace(/^\.+/, "") || "repo"}`;
|
|
1840
|
+
}
|
|
1841
|
+
//#endregion
|
|
1371
1842
|
//#region src/provider.ts
|
|
1372
1843
|
const e2bSandboxProvider = {
|
|
1373
1844
|
id: "e2b",
|
|
@@ -1618,6 +2089,6 @@ var E2BCodeModeTransport = class {
|
|
|
1618
2089
|
}
|
|
1619
2090
|
};
|
|
1620
2091
|
//#endregion
|
|
1621
|
-
export { E2BCodeModeTransport, E2BProcessManager, E2BSandbox, createDefaultMountableTemplate, e2bSandboxProvider };
|
|
2092
|
+
export { DEFAULT_NODE_VERSION, E2BCodeModeTransport, E2BProcessManager, E2BSandbox, createDefaultMountableTemplate, createRepoTemplate, e2bSandboxProvider, isDeferredNamedTemplateSpec, isNamedTemplateSpec, refreshRepoTemplate, repoTemplateRef };
|
|
1622
2093
|
|
|
1623
2094
|
//# sourceMappingURL=index.js.map
|