e2b
Version:
E2B SDK that give agents cloud environments
8,843 lines • 356 kB
JavaScript
Object.defineProperties(exports, {
__esModule: { value: true },
[Symbol.toStringTag]: { value: "Module" }
});
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let openapi_fetch = require("openapi-fetch");
openapi_fetch = __toESM(openapi_fetch);
let platform = require("platform");
platform = __toESM(platform);
let compare_versions = require("compare-versions");
let _connectrpc_connect = require("@connectrpc/connect");
let _connectrpc_connect_protocol_connect = require("@connectrpc/connect/protocol-connect");
let _bufbuild_protobuf_codegenv2 = require("@bufbuild/protobuf/codegenv2");
let _bufbuild_protobuf_wkt = require("@bufbuild/protobuf/wkt");
let _bufbuild_protobuf = require("@bufbuild/protobuf");
let _connectrpc_connect_web = require("@connectrpc/connect-web");
let node_fs = require("node:fs");
node_fs = __toESM(node_fs);
let node_stream = require("node:stream");
node_stream = __toESM(node_stream);
let chalk = require("chalk");
chalk = __toESM(chalk);
let node_crypto = require("node:crypto");
node_crypto = __toESM(node_crypto);
let node_os = require("node:os");
node_os = __toESM(node_os);
let node_path = require("node:path");
node_path = __toESM(node_path);
let node_url = require("node:url");
node_url = __toESM(node_url);
let _e2b_dockerfile_utils = require("@e2b/dockerfile-utils");
//#region package.json
var version = "2.64.0";
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/typeof.js
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
return typeof o;
} : function(o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/toPrimitive.js
function toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/toPropertyKey.js
function toPropertyKey(t) {
var i = toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/defineProperty.js
function _defineProperty(e, r, t) {
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
//#endregion
//#region src/errors.ts
const SANDBOX_NOT_FOUND_MESSAGE = "was not found";
const SANDBOX_PORT_NOT_OPEN_MESSAGE = "port is not open";
function isSandboxNotFoundMessage(message) {
return message.includes(SANDBOX_NOT_FOUND_MESSAGE);
}
function isSandboxPortNotOpenMessage(message) {
return message.includes(SANDBOX_PORT_NOT_OPEN_MESSAGE);
}
/**
* Maps a 502/Unavailable answered by the proxy in front of envd: the sandbox was not
* found (killed or reached its timeout) is a `SandboxNotRunningError`; the sandbox
* running but envd's port not open (e.g. its network is down) a
* `SandboxUnreachableError`; any other message (e.g. envd ending a stream because
* the connection to the sandbox was lost) a `SandboxUnreachableError` too, with the
* sandbox state reported as unknown.
*/
function formatSandboxUnavailableError(message) {
if (isSandboxNotFoundMessage(message)) return new SandboxNotRunningError(`${message}: The sandbox was killed or reached its timeout. You can modify the sandbox timeout by passing 'timeoutMs' when starting the sandbox or calling '.setTimeout' on the sandbox with the desired timeout.`);
if (isSandboxPortNotOpenMessage(message)) return new SandboxUnreachableError(`${message}: The sandbox is running but envd inside it could not be reached (e.g. its network is down). Check the sandbox state with 'Sandbox.getInfo()'.`);
return new SandboxUnreachableError(`${message}: The sandbox could not be reached and its state is unknown — it may have been killed or reached its timeout, or the connection to it was lost. Check the sandbox state with 'Sandbox.getInfo()'.`);
}
/**
* Base class for all sandbox errors.
*
* Thrown when general sandbox errors occur.
*/
var SandboxError = class extends Error {
constructor(message) {
super(message);
_defineProperty(
this,
/**
* HTTP status of the API response that produced this error, when there was one.
*/
"statusCode",
void 0
);
this.name = "SandboxError";
}
};
/**
* Thrown when a timeout error occurs.
*
* The [unavailable] error type is caused by sandbox timeout.
*
* The [canceled] error type is caused by exceeding request timeout.
*
* The [deadline_exceeded] error type is caused by exceeding the timeout for command execution, watch, etc.
*
* The [unknown] error type is sometimes caused by the sandbox timeout when the request is not processed correctly.
*/
var TimeoutError = class extends SandboxError {
constructor(message) {
super(message);
this.name = "TimeoutError";
}
};
/**
* Thrown when an invalid argument is provided.
*/
var InvalidArgumentError = class extends SandboxError {
constructor(message, stackTrace) {
super(message);
this.name = "InvalidArgumentError";
if (stackTrace) this.stack = stackTrace;
}
};
/**
* Thrown when there is not enough disk space.
*/
var NotEnoughSpaceError = class extends SandboxError {
constructor(message) {
super(message);
this.name = "NotEnoughSpaceError";
}
};
/**
* Thrown when a resource is not found.
*
* @deprecated Use {@link FileNotFoundError} or {@link SandboxNotFoundError} instead. This class will be removed in the next major version.
*/
var NotFoundError = class extends SandboxError {
constructor(message) {
super(message);
this.name = "NotFoundError";
}
};
/**
* Thrown when a file or directory is not found inside a sandbox.
*/
var FileNotFoundError = class extends NotFoundError {
constructor(message) {
super(message);
this.name = "FileNotFoundError";
}
};
/**
* Thrown when a sandbox is not found (e.g. it doesn't exist or is no longer running).
*/
var SandboxNotFoundError = class extends NotFoundError {
constructor(message) {
super(message);
this.name = "SandboxNotFoundError";
}
};
/**
* Thrown when the sandbox is not running anymore: the proxy in front of it
* answered a request (or the health probe run after a request failed at the
* connection level) with the sandbox not found — it was killed, paused or
* reached its timeout. Retrying the request will not help; create or resume a
* sandbox.
*
* The split from `SandboxUnreachableError` is only as good as the proxy's answer
* and the health probe: a sandbox that is gone but could not be asked about is a
* `SandboxUnreachableError`. `SandboxNotFoundError` is only raised by the control
* plane API (e.g. `Sandbox.connect()` to a sandbox that does not exist).
*
* Subclass of `TimeoutError`, which this case surfaced as before.
*/
var SandboxNotRunningError = class extends TimeoutError {
constructor(message, options) {
super(message);
this.name = "SandboxNotRunningError";
if ((options === null || options === void 0 ? void 0 : options.cause) !== void 0) this.cause = options.cause;
}
};
/**
* Thrown when the sandbox could not be reached while it is not confirmed to be
* stopped: the proxy in front of the sandbox reports it running but envd's port
* not open (e.g. the sandbox's network is down), the proxy or envd answered
* with an unavailability the health probe could not resolve (e.g. the connection
* to the sandbox was lost mid-stream while the probe found it running), or a
* request failed at the connection level (the connection could not be
* established or was dropped mid-request) and the follow-up health probe got
* no answer from the sandbox either. A sandbox confirmed stopped (or paused) is
* a `SandboxNotRunningError` — but only as far as the probe can tell: a sandbox
* that is gone and could not be asked about surfaces here.
*
* This usually means the sandbox's network is down, envd inside it is not up
* (yet), or a network issue between the client and the sandbox. Check the
* sandbox state with `Sandbox.getInfo()`.
*
* Subclass of `TimeoutError`, which these cases surfaced as before.
*/
var SandboxUnreachableError = class extends TimeoutError {
constructor(message, options) {
super(message);
this.name = "SandboxUnreachableError";
if ((options === null || options === void 0 ? void 0 : options.cause) !== void 0) this.cause = options.cause;
}
};
/**
* Thrown when authentication fails.
*/
var AuthenticationError = class extends Error {
constructor(message) {
super(message);
this.name = "AuthenticationError";
}
};
/**
* Thrown when git authentication fails.
*
* @deprecated Run git with `sandbox.commands.run()` instead. The git module will be removed in the next major version.
*/
var GitAuthError = class extends AuthenticationError {
constructor(message) {
super(message);
this.name = "GitAuthError";
}
};
/**
* Thrown when git upstream tracking is missing.
*
* @deprecated Run git with `sandbox.commands.run()` instead. The git module will be removed in the next major version.
*/
var GitUpstreamError = class extends SandboxError {
constructor(message) {
super(message);
this.name = "GitUpstreamError";
}
};
/**
* Thrown when the template uses old envd version. It isn't compatible with the new SDK.
*/
var TemplateError = class extends SandboxError {
constructor(message, stackTrace) {
super(message);
this.name = "TemplateError";
if (stackTrace) this.stack = stackTrace;
}
};
/**
* Thrown when the API rate limit is exceeded.
*/
var RateLimitError = class extends SandboxError {
constructor(message) {
super(message);
this.name = "RateLimitError";
this.statusCode = 429;
}
};
/**
* Thrown when the API refused the operation because the service is
* temporarily busy (HTTP 503): no capacity to place a sandbox right now, or
* the node running the sandbox declined a request it cannot serve yet.
*
* Nothing was changed by the refused call: for example a refused pause
* leaves the sandbox running with its state intact, so the same call can be
* retried after a short wait.
*
* Like {@link AuthenticationError} and unlike the other API errors, this is
* not a {@link SandboxError}: it is raised for every 503 whatever the
* operation, so catch it explicitly.
*/
var ServiceBusyError = class extends Error {
constructor(message) {
super(message);
_defineProperty(this, "statusCode", 503);
this.name = "ServiceBusyError";
}
};
/**
* Thrown when a paused sandbox cannot be resumed with its memory because its
* snapshot was taken while the sandbox's guest agent (envd) was not
* responding (`envdUnresponsiveAtPause` in sandbox info). Restoring that
* memory would bring the unresponsive agent back; resume the sandbox with
* `onResume: 'reboot'`, which cold-boots it from disk.
*/
var GuestUnresponsiveError = class extends SandboxError {
constructor(message) {
super(message);
this.name = "GuestUnresponsiveError";
this.statusCode = 409;
}
};
_defineProperty(GuestUnresponsiveError, "code", "sandbox_snapshot_guest_agent_unresponsive");
/**
* Thrown when the build fails.
*/
var BuildError = class extends Error {
constructor(message, stackTrace) {
super(message);
this.name = "BuildError";
if (stackTrace) this.stack = stackTrace;
}
};
/**
* Thrown when the file upload fails.
*/
var FileUploadError = class extends BuildError {
constructor(message, stackTrace) {
super(message, stackTrace);
this.name = "FileUploadError";
}
};
/**
* Base class for all volume errors.
*
* Thrown when general volume errors occur.
*/
var VolumeError = class extends Error {
constructor(message) {
super(message);
this.name = "VolumeError";
}
};
/**
* Thrown when a volume is not found.
*/
var VolumeNotFoundError = class extends VolumeError {
constructor(message) {
super(message);
this.name = "VolumeNotFoundError";
}
};
/**
* Thrown when a file or directory is not found inside a volume.
*/
var VolumePathNotFoundError = class extends VolumeError {
constructor(message) {
super(message);
this.name = "VolumePathNotFoundError";
}
};
/**
* Base class for all secret errors.
*
* Thrown when general secret errors occur.
*/
var SecretError = class extends Error {
constructor(message) {
super(message);
this.name = "SecretError";
}
};
/**
* Thrown when a secret is not found.
*/
var SecretNotFoundError = class extends SecretError {
constructor(message) {
super(message);
this.name = "SecretNotFoundError";
}
};
//#endregion
//#region src/is.ts
/**
* Class-agnostic checks for web platform objects.
*
* `value instanceof Blob` does not answer "is this a Blob", it answers "was
* this minted by *the* `Blob` class this module happens to see". In a Node
* process those are different questions: libraries replace the web globals the
* same way they replace `globalThis.fetch` — `@hono/node-server` installs its
* own `Request`, remix's `installGlobals()` swaps `Request`/`Blob`/`File`,
* `web-streams-polyfill` swaps `ReadableStream`, jsdom-based test environments
* bring their own copies of all of them — and values also cross realms
* (`node:vm`, `worker_threads`). A perfectly good Blob then fails that check —
* a *brand* check, in spec terms — and the SDK silently takes the wrong branch.
*
* The failure modes are not theoretical; each one is covered by a test:
* - a Request the current global class disowns is handed to undici verbatim and
* every API call dies with `Failed to parse URL from [object Request]`;
* - a foreign `Blob` or `ReadableStream` body is stringified by the platform,
* so the upload silently contains the text `[object Blob]`;
* - a foreign `ReadableStream` upload is buffered into memory instead of
* streamed, or hangs when piped through `CompressionStream`;
* - a foreign `Blob` response body reads back as an empty file.
*
* So ask what a value *is*, not who made it: keep `instanceof` as the fast
* path, then fall back to the members and `Symbol.toStringTag` the platform
* guarantees. Detection is only half of it — see `toBlob`/`toUploadBody` in
* `utils.ts`, which convert what they detect into a native equivalent before
* handing it to the platform.
*/
function isObject(value) {
return typeof value === "object" && value !== null;
}
/**
* The value's `Symbol.toStringTag`, e.g. `'Blob'` for anything implementing the
* `Blob` interface. Spec'd for every web platform interface and inherited by
* subclasses, so it survives both realm and class swaps. Same one-liner
* `@sindresorhus/is` uses for the types it covers (`Blob`, `ArrayBuffer`; it has
* no `Request` or `ReadableStream` check, which is why this module exists).
*/
function platformTag(value) {
return Object.prototype.toString.call(value).slice(8, -1);
}
/**
* Whether `value` should be treated as a `Request`.
*
* Duck-typed on `url` + `method` + `clone` rather than on the tag, because
* older `fetch` ponyfills predate `Symbol.toStringTag`; `clone` is what
* separates a `Request` from other `{ url, method }` carriers such as Node's
* `IncomingMessage`.
*/
function isRequestLike(value) {
return value instanceof Request || isObject(value) && typeof value.url === "string" && typeof value.method === "string" && typeof value.clone === "function";
}
/**
* Whether `value` should be treated as a `Blob` (or a `File`, which is a
* `Blob`).
*
* `arrayBuffer` is the only member required beyond the tag, because reading the
* bytes is all the SDK ever does with a Blob it didn't make — asking for
* `stream` too would turn implementations that lack it into corrupted uploads
* for no gain.
*/
function isBlobLike(value) {
if (value instanceof Blob) return true;
if (!isObject(value)) return false;
const tag = platformTag(value);
return (tag === "Blob" || tag === "File") && typeof value.arrayBuffer === "function";
}
/**
* Whether `value` should be treated as a `ReadableStream`.
*
* `getReader` + `tee` + `cancel` is unmistakable enough to skip the tag, which
* keeps this working for stream implementations that only got a
* `Symbol.toStringTag` in later versions.
*/
function isReadableStreamLike(value) {
return value instanceof ReadableStream || isObject(value) && typeof value.getReader === "function" && typeof value.tee === "function" && typeof value.cancel === "function";
}
/**
* Whether `value` is an async iterable (e.g. an async generator or a Node.js
* `Readable`). A `ReadableStream` is one too in most runtimes, so check
* `isReadableStreamLike` first.
*/
function isAsyncIterable(value) {
return isObject(value) && typeof value[Symbol.asyncIterator] === "function";
}
/**
* Whether `value` should be treated as an `ArrayBuffer`.
*
* Unlike the others this needs no conversion afterwards: the platform detects
* buffer sources through V8 rather than by brand, so a cross-realm
* `ArrayBuffer` is already accepted everywhere a native one is.
*/
function isArrayBufferLike(value) {
return value instanceof ArrayBuffer || isObject(value) && platformTag(value) === "ArrayBuffer";
}
//#endregion
//#region src/utils.ts
function getRuntime() {
var _navigator, _process;
if (globalThis.Bun) return {
runtime: "bun",
version: globalThis.Bun.version
};
if (globalThis.Deno) return {
runtime: "deno",
version: globalThis.Deno.version.deno
};
if (typeof EdgeRuntime === "string") return {
runtime: "vercel-edge",
version: "unknown"
};
if (((_navigator = globalThis.navigator) === null || _navigator === void 0 ? void 0 : _navigator.userAgent) === "Cloudflare-Workers") return {
runtime: "cloudflare-worker",
version: "unknown"
};
if (((_process = globalThis.process) === null || _process === void 0 || (_process = _process.release) === null || _process === void 0 ? void 0 : _process.name) === "node") return {
runtime: "node",
version: platform.default.version || "unknown"
};
if (typeof window !== "undefined") return {
runtime: "browser",
version: platform.default.version || "unknown"
};
return {
runtime: "unknown",
version: "unknown"
};
}
const { runtime, version: runtimeVersion } = getRuntime();
async function sha256(data) {
const dataBuffer = new TextEncoder().encode(data);
const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer);
const hashArray = new Uint8Array(hashBuffer);
return btoa(String.fromCharCode(...hashArray));
}
function timeoutToSeconds(timeout) {
return Math.ceil(timeout / 1e3);
}
/**
* Import an optional, runtime-resolved package (e.g. `undici`, `glob`, `tar`)
* without letting downstream bundlers resolve it at build time.
*
* The variable specifier plus the `webpackIgnore`/`@vite-ignore` annotations
* keep the import opaque to bundlers, so browser/edge builds don't try to
* pull node-only packages into the bundle, while plain Node resolves it
* natively at runtime.
*/
async function dynamicImport(module) {
if (runtime === "browser") throw new Error("Browser runtime is not supported for dynamic import");
return await import(
/* webpackIgnore: true */
/* @vite-ignore */
module
);
}
function ansiRegex({ onlyFirst = false } = {}) {
return new RegExp(`(?:\\u001B[\\]PX^_][\\s\\S]*?(?:\\u0007|\\u001B\\u005C|\\u009C))|[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]`, onlyFirst ? void 0 : "g");
}
function stripAnsi(text) {
return text.replace(ansiRegex(), "");
}
/**
* The platform's own `ReadableStream` class. A Blob's stream is created by the
* platform, so this holds even when `globalThis.ReadableStream` was replaced.
* Falls back to the global when `Blob` itself was replaced by one without
* `stream()` (jsdom).
*/
function platformStreamClass() {
try {
var _Blob$stream, _Blob;
const stream = (_Blob$stream = (_Blob = new Blob()).stream) === null || _Blob$stream === void 0 ? void 0 : _Blob$stream.call(_Blob);
if (stream) return stream.constructor;
} catch (_unused) {}
return ReadableStream;
}
/**
* Adopt a stream from a polyfill, a replaced global, or another realm into the
* platform's `ReadableStream` class by pumping it through a new one. Reading
* through the public reader is the portable part of a stream.
*
* Needed wherever the stream is handed to another platform primitive that
* brand-checks it — `pipeThrough(new CompressionStream(…))` on a foreign stream
* never settles.
*/
function toNativeStream(stream) {
const PlatformStream = platformStreamClass();
if (stream instanceof PlatformStream) return stream;
const reader = stream.getReader();
return new PlatformStream({
async pull(controller) {
const { done, value } = await reader.read();
if (done) {
controller.close();
return;
}
controller.enqueue(value);
},
cancel(reason) {
return reader.cancel(reason);
}
});
}
/**
* Adopt a stream only if the platform would not accept it as a request body —
* handed one it doesn't accept, it stringifies it to
* `"[object ReadableStream]"`.
*
* Two kinds are accepted: the platform's own stream class, and any async
* iterable. Async iterability is the half that survives a replaced global — a
* native stream stays async-iterable even when `globalThis.ReadableStream` is a
* polyfill — so re-wrapping one of those would only trade a stream the platform
* accepts for one it may not.
*/
function toDispatchableStream(stream) {
return stream instanceof platformStreamClass() || Symbol.asyncIterator in stream ? stream : toNativeStream(stream);
}
/**
* Whether `data` is streamed rather than buffered.
*/
function isStreamableData(data) {
return isReadableStreamLike(data) || isAsyncIterable(data);
}
/**
* Validate a chunk and copy it: a producer may reuse its buffer after
* yielding, before the transport has sent it. The copy also adopts a
* `Uint8Array` from another realm (iframe, `node:vm`, worker), which fails
* `instanceof` but carries the realm-independent tag.
*/
function checkChunk(chunk) {
var _chunk$constructor$na, _chunk$constructor;
if (chunk instanceof Uint8Array || ArrayBuffer.isView(chunk) && Object.prototype.toString.call(chunk) === "[object Uint8Array]") return new Uint8Array(chunk);
throw new InvalidArgumentError(`AsyncIterable upload data must yield Uint8Array chunks, got ${chunk === null ? "null" : typeof chunk === "object" ? (_chunk$constructor$na = (_chunk$constructor = chunk.constructor) === null || _chunk$constructor === void 0 ? void 0 : _chunk$constructor.name) !== null && _chunk$constructor$na !== void 0 ? _chunk$constructor$na : "object" : typeof chunk}`);
}
/**
* Wrap an async iterable of byte chunks in a native `ReadableStream`, the one
* streamed body every runtime's `fetch` accepts.
*/
function asyncIterableToStream(iterable, onInvalidChunk) {
const iterator = iterable[Symbol.asyncIterator]();
return new (platformStreamClass())({
async pull(controller) {
const { done, value } = await iterator.next();
if (done) {
controller.close();
return;
}
let chunk;
try {
chunk = checkChunk(value);
} catch (err) {
var _iterator$return;
await ((_iterator$return = iterator.return) === null || _iterator$return === void 0 ? void 0 : _iterator$return.call(iterator).catch(() => {}));
onInvalidChunk(err);
throw err;
}
controller.enqueue(chunk);
},
async cancel(reason) {
var _iterator$return2;
await ((_iterator$return2 = iterator.return) === null || _iterator$return2 === void 0 ? void 0 : _iterator$return2.call(iterator, reason));
}
});
}
/**
* Convert in-memory data to a Blob, avoiding unnecessary conversions when
* possible. Streams are never buffered: their final size is unknown.
*/
async function toBlob(data) {
if (data instanceof Blob) return data;
if (isBlobLike(data)) return new Blob([await data.arrayBuffer()], { type: data.type });
return new Blob([data]);
}
const UNSAFE_SHELL_CHAR = /[^\w@%+=:,./-]/;
/**
* Quote a string for safe interpolation into a POSIX shell command.
*
* Faithful port of Python's `shlex.quote`: an empty string becomes `''`,
* values containing only safe characters are returned unchanged (keeping
* generated commands stable and cache-friendly), and anything else is wrapped
* in single quotes with embedded single quotes escaped as `'"'"'`.
*/
function shellQuote(s) {
if (s === "") return "''";
if (!UNSAFE_SHELL_CHAR.test(s)) return s;
return "'" + s.replace(/'/g, "'\"'\"'") + "'";
}
/**
* Whether `fetch` can send a `ReadableStream` request body. Browsers without
* support ignore `duplex` and stringify the stream, giving it a text type.
*/
function supportsRequestStreams() {
let duplexAccessed = false;
const PlatformStream = platformStreamClass();
const hasContentType = new Request("https://e2b.invalid", {
body: new PlatformStream(),
method: "POST",
get duplex() {
duplexAccessed = true;
return "half";
}
}).headers.has("Content-Type");
return duplexAccessed && !hasContentType;
}
/**
* Prepare data for upload, optionally gzip-compressed.
*
* Streams are always uploaded as a `ReadableStream`, never buffered, in every
* runtime; a browser that can't stream request bodies throws. In-memory
* data is sent as a Blob, or as a gzip-compressed stream outside the browser.
*/
async function toUploadBody(data, gzip) {
if (isStreamableData(data)) {
if (runtime === "browser" && !supportsRequestStreams()) throw new InvalidArgumentError("This browser can't stream request bodies, and stream data is never buffered. Pass the data as a `Blob` instead, e.g. `await new Response(stream).blob()`.");
let sourceError;
const stream = isAsyncIterable(data) && !isReadableStreamLike(data) ? asyncIterableToStream(data, (err) => {
sourceError = err;
}) : gzip || runtime === "browser" ? toNativeStream(data) : toDispatchableStream(data);
return {
body: gzip ? stream.pipeThrough(new CompressionStream("gzip")) : stream,
streamed: true,
sourceError: () => sourceError
};
}
const noSourceError = () => void 0;
if (gzip) {
const compressed = (await toBlob(data)).stream().pipeThrough(new CompressionStream("gzip"));
return runtime === "browser" ? {
body: await new Response(compressed).blob(),
streamed: false,
sourceError: noSourceError
} : {
body: compressed,
streamed: true,
sourceError: noSourceError
};
}
return {
body: await toBlob(data),
streamed: false,
sourceError: noSourceError
};
}
//#endregion
//#region src/api/metadata.ts
var _platform$os;
const defaultHeaders = {
browser: typeof window !== "undefined" && platform.default.name || "unknown",
lang: "js",
lang_version: runtimeVersion,
package_version: version,
publisher: "e2b",
sdk_runtime: runtime,
system: ((_platform$os = platform.default.os) === null || _platform$os === void 0 ? void 0 : _platform$os.family) || "unknown"
};
function getEnvVar(name) {
if (runtime === "deno") return Deno.env.get(name);
if (typeof process === "undefined") return "";
return process.env[name];
}
/**
* Parse an env var as a base-10 integer, falling back to `defaultValue` when
* the env var is unset. Throws on non-integer input rather than silently
* falling back so misconfiguration is surfaced loudly.
*/
function parseIntEnv(name, defaultValue) {
const raw = getEnvVar(name);
if (!raw) return defaultValue;
const parsed = Number.parseInt(raw, 10);
if (!Number.isFinite(parsed)) throw new Error(`Invalid ${name}=${JSON.stringify(raw)}: expected an integer.`);
return parsed;
}
/**
* Parse an env var that must be a positive integer (>= 1). Throws on
* non-positive or non-integer input.
*/
function parsePositiveIntEnv(name, defaultValue) {
const parsed = parseIntEnv(name, defaultValue);
if (parsed < 1) throw new Error(`Invalid ${name}=${parsed}: expected a positive integer.`);
return parsed;
}
//#endregion
//#region src/retry.ts
const MAX_RETRY_AFTER_SECONDS = 2147483;
const MAX_RETRY_WAIT_WITHOUT_TIMEOUT_MS = 6e4;
const BACKOFF_BASE_MS = 100;
const BACKOFF_MAX_MS = 1e4;
const BACKOFF_JITTER_MIN = .5;
const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
429,
502,
503,
504
]);
const CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
"ECONNREFUSED",
"ENOTFOUND",
"EAI_AGAIN",
"EHOSTUNREACH",
"ENETUNREACH",
"ENETDOWN",
"EHOSTDOWN",
"UND_ERR_CONNECT_TIMEOUT",
"ConnectionRefused"
]);
const CONNECTION_ERROR_SYSCALLS = /* @__PURE__ */ new Set(["connect", "getaddrinfo"]);
const DENO_CONNECTION_ERROR = /client error \(Connect\)/;
const OPAQUE_NETWORK_ERROR = /^(Failed to fetch|Load failed|NetworkError when attempting to fetch resource\.?)$/;
function resolveRetries(retries) {
if (!Number.isInteger(retries) || retries < 0) throw new InvalidArgumentError(`Invalid retries=${retries}: expected a non-negative integer.`);
return retries;
}
const IDEMPOTENCY_KEY_PATTERN = /^[a-z0-9-]{1,255}$/;
function resolveIdempotencyKey(key) {
if (key === void 0) return void 0;
if (key.length < 1 || key.length > 255) throw new InvalidArgumentError(`Invalid idempotencyKey: expected 1-255 characters, got ${key.length}.`);
if (!IDEMPOTENCY_KEY_PATTERN.test(key)) throw new InvalidArgumentError("Invalid idempotencyKey: only lowercase letters, digits and hyphens are allowed.");
return key;
}
function parseRetryAfter(value) {
if (!value) return void 0;
const trimmed = value.trim();
if (!/^\d+$/.test(trimmed)) return void 0;
const delay = Number(trimmed);
return Number.isSafeInteger(delay) && delay <= MAX_RETRY_AFTER_SECONDS ? delay : void 0;
}
/**
* Whether `error` (thrown by `fetch`) is a failure to establish the connection.
* Walks `cause` chains and `AggregateError` members, as Node wraps the socket
* error in a `TypeError('fetch failed')`.
*/
function isConnectionError(error, depth = 0) {
if (!(error instanceof Error) || depth > 4) return false;
const { code, syscall } = error;
if (typeof code === "string" && CONNECTION_ERROR_CODES.has(code)) return true;
if (typeof syscall === "string" && CONNECTION_ERROR_SYSCALLS.has(syscall)) return true;
if (DENO_CONNECTION_ERROR.test(error.message)) return true;
if (error instanceof AggregateError) return error.errors.some((member) => isConnectionError(member, depth + 1));
return isConnectionError(error.cause, depth + 1);
}
/**
* Whether `error` (thrown by `fetch`) is a network failure: a connection error
* (see {@link isConnectionError}) or the opaque `TypeError` browsers raise for any
* network failure, which carries no code or syscall.
*/
function isNetworkError(error) {
if (isConnectionError(error)) return true;
return error instanceof Error && OPAQUE_NETWORK_ERROR.test(error.message);
}
/**
* Whether a `fetch` rejection is a connection-establishment failure. Aborts
* and failures after the request may have been sent are not retried.
*/
function isRetryableFetchError(error) {
if (error instanceof DOMException) return false;
return isConnectionError(error);
}
function wait(delayMs, signal) {
if (signal.aborted) return Promise.reject(signal.reason);
return new Promise((resolve, reject) => {
const onAbort = () => {
clearTimeout(timer);
reject(signal.reason);
};
const timer = setTimeout(() => {
signal.removeEventListener("abort", onAbort);
resolve();
}, delayMs);
signal.addEventListener("abort", onAbort, { once: true });
});
}
function backoffMs(attempt, random) {
const backoff = Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_MAX_MS);
return Math.floor(backoff * (BACKOFF_JITTER_MIN + random() * .5));
}
/**
* Delay before the next attempt, or `undefined` unless `Should-Retry` is true
* or the header is absent for a retryable status. A present header is
* authoritative, including explicit `false`.
*/
function retryDelayMs(response, attempt, random) {
const shouldRetry = response.headers.get("Should-Retry");
if (!(shouldRetry === null ? RETRYABLE_STATUSES.has(response.status) : shouldRetry.trim().toLowerCase() === "true")) return void 0;
const retryAfter = parseRetryAfter(response.headers.get("Retry-After"));
if (retryAfter !== void 0) return retryAfter * 1e3;
return backoffMs(attempt, random);
}
/**
* Retries API responses marked `Should-Retry: true`, or 429/502/503/504
* responses without the header, after `Retry-After` or exponential backoff.
* Also retries connection-establishment failures.
*/
function withRetry(fetchImpl, retries, requestTimeoutMs, dependencies = {}) {
var _dependencies$monoton, _dependencies$sleep, _dependencies$random;
const monotonic = (_dependencies$monoton = dependencies.monotonic) !== null && _dependencies$monoton !== void 0 ? _dependencies$monoton : (() => performance.now());
const sleep = (_dependencies$sleep = dependencies.sleep) !== null && _dependencies$sleep !== void 0 ? _dependencies$sleep : wait;
const random = (_dependencies$random = dependencies.random) !== null && _dependencies$random !== void 0 ? _dependencies$random : Math.random;
return (async (input, init) => {
if (retries === 0 || isReadableStreamLike(init === null || init === void 0 ? void 0 : init.body)) return fetchImpl(input, init);
const request = input instanceof Request && init === void 0 ? input : new Request(input, init);
const deadline = monotonic() + (requestTimeoutMs || MAX_RETRY_WAIT_WITHOUT_TIMEOUT_MS);
for (let attempt = 0;; attempt++) {
var _response$body;
let response;
try {
response = await fetchImpl(attempt === retries ? request : request.clone());
} catch (error) {
if (attempt === retries || request.signal.aborted || !isRetryableFetchError(error)) throw error;
const delayMs = backoffMs(attempt, random);
if (monotonic() + delayMs >= deadline) throw error;
await sleep(delayMs, request.signal);
continue;
}
if (attempt === retries) return response;
const delayMs = retryDelayMs(response, attempt, random);
if (delayMs === void 0 || monotonic() + delayMs >= deadline) return response;
(_response$body = response.body) === null || _response$body === void 0 || _response$body.cancel().catch(() => {});
await sleep(delayMs, request.signal);
}
});
}
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/objectSpread2.js
function ownKeys(e, r) {
var t = Object.keys(e);
if (Object.getOwnPropertySymbols) {
var o = Object.getOwnPropertySymbols(e);
r && (o = o.filter(function(r) {
return Object.getOwnPropertyDescriptor(e, r).enumerable;
})), t.push.apply(t, o);
}
return t;
}
function _objectSpread2(e) {
for (var r = 1; r < arguments.length; r++) {
var t = null != arguments[r] ? arguments[r] : {};
r % 2 ? ownKeys(Object(t), !0).forEach(function(r) {
_defineProperty(e, r, t[r]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r) {
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
});
}
return e;
}
//#endregion
//#region src/connectionConfig.ts
const supportedDomains = [
"e2b.app",
"e2b.dev",
"e2b.pro",
"e2b-staging.dev",
"e2b-tango.dev",
"e2b-juliett.dev",
"foxtrot-2.e2b.dev"
];
const REQUEST_TIMEOUT_MS = 6e4;
const STREAM_INPUT_TIMEOUT_MS = 6e4;
const KEEPALIVE_PING_HEADER = "Keepalive-Ping-Interval";
/**
* The HTTP version to use: the explicit value, else `E2B_HTTP_VERSION`, else
* {@link DEFAULT_HTTP_VERSION}. Rejects anything but `'1.1'` or `'2'`.
*/
function resolveHttpVersion(httpVersion) {
let source = "httpVersion";
if (httpVersion === void 0) {
source = "E2B_HTTP_VERSION";
httpVersion = getEnvVar("E2B_HTTP_VERSION") || "2";
}
if (httpVersion !== "1.1" && httpVersion !== "2") throw new InvalidArgumentError(`${source} must be '1.1' or '2', got '${httpVersion}'`);
return httpVersion;
}
/**
* Build an `AbortSignal` that combines an optional request-timeout signal
* (via `AbortSignal.timeout`) with an optional user-provided signal.
*
* Returns `undefined` when neither input would produce a signal.
*
* @internal
*/
function buildRequestSignal(requestTimeoutMs, userSignal) {
const timeoutSignal = requestTimeoutMs ? AbortSignal.timeout(requestTimeoutMs) : void 0;
if (timeoutSignal && userSignal) return AbortSignal.any([timeoutSignal, userSignal]);
return timeoutSignal !== null && timeoutSignal !== void 0 ? timeoutSignal : userSignal;
}
/**
* Set up an internal `AbortController` for a streaming request.
*
* Until `clearStartTimeout` is called, the controller aborts when either
* - the optional user signal aborts, or
* - the optional request timeout elapses (used to bound the initial
* handshake; long-lived streams should call `clearStartTimeout` once
* the handshake succeeds).
*
* The user-signal listener stays attached for the full stream lifetime
* so the caller can cancel a long-running stream by aborting the signal.
*
* `cleanup` is idempotent and detaches the listener, clears the handshake
* timer (if still pending), and aborts the controller. Call it when the
* stream finishes or when startup fails.
*
* @internal
*/
function setupRequestController(requestTimeoutMs, userSignal) {
const controller = new AbortController();
const onUserAbort = () => abortWithReason(controller, userSignal === null || userSignal === void 0 ? void 0 : userSignal.reason);
if (userSignal) {
if (userSignal.aborted) abortWithReason(controller, userSignal.reason);
else userSignal.addEventListener("abort", onUserAbort, { once: true });
}
let reqTimeout = requestTimeoutMs ? setTimeout(() => abortWithReason(controller, new DOMException(`Request handshake timed out after ${requestTimeoutMs}ms`, "TimeoutError")), requestTimeoutMs) : void 0;
const clearStartTimeout = () => {
if (reqTimeout) {
clearTimeout(reqTimeout);
reqTimeout = void 0;
}
};
let cleaned = false;
const cleanup = () => {
if (cleaned) return;
cleaned = true;
userSignal === null || userSignal === void 0 || userSignal.removeEventListener("abort", onUserAbort);
clearStartTimeout();
controller.abort();
};
return {
controller,
clearStartTimeout,
cleanup
};
}
/**
* Create a resettable idle-timeout that aborts `controller` when no progress is
* made within `idleTimeoutMs`. `arm` (re)starts the timer; call it on each
* chunk. `clear` stops it. `0`/`undefined` disables it (both are no-ops).
*
* @internal
*/
function createIdleAbort(controller, idleTimeoutMs, label) {
let timer;
const clear = () => {
if (timer) {
clearTimeout(timer);
timer = void 0;
}
};
const arm = () => {
if (!idleTimeoutMs) return;
clear();
timer = setTimeout(() => abortWithReason(controller, new DOMException(`${label} idle for ${idleTimeoutMs}ms`, "TimeoutError")), idleTimeoutMs);
};
return {
arm,
clear
};
}
/**
* Abort with the reason pinned to the controller. Bun (observed on 1.3.14)
* holds `signal.reason` weakly: a reason that nothing else strongly
* references — e.g. a `DOMException` constructed inside a timer callback —
* can be garbage-collected, leaving `signal.reason` undefined by the time a
* consumer reads it. Pinning the reason to the controller keeps it alive for
* the signal's lifetime. No-op cost on other runtimes.
*
* @internal
*/
function abortWithReason(controller, reason) {
if (controller.signal.aborted) return;
controller.__e2bAbortReason = reason;
controller.abort(reason);
}
/**
* Wrap a streaming response body so its pooled connection is released when the
* stream is fully read, cancelled, errors, or stays idle for too long.
*
* Clears the handshake timeout from {@link setupRequestController} (so
* consuming the body isn't killed by it) and replaces it with an idle-read
* timeout that bounds only the wire: it's armed while waiting on a network
* read and cleared the moment a chunk arrives, so a slow or paused consumer
* never trips it (only a server that stops sending mid-stream does). On expiry
* it aborts `controller`, tearing down the fetch and releasing the connection.
* Pass `0`/`undefined` to disable. Call once the handshake has succeeded.
*
* @internal
*/
function wrapStreamWithConnectionCleanup(body, { clearStartTimeout, cleanup, controller, idleTimeoutMs }) {
clearStartTimeout();
if (!body) {
cleanup();
return new Blob([]).stream();
}
const reader = body.getReader();
const idle = createIdleAbort(controller, idleTimeoutMs, "Stream");
let released = false;
const release = () => {
if (released) return;
released = true;
idle.clear();
cleanup();
};
return new ReadableStream({
async pull(streamController) {
idle.arm();
try {
const { done, value } = await reader.read();
idle.clear();
if (done) {
release();
streamController.close();
} else streamController.enqueue(value);
} catch (err) {
release();
streamController.error(err);
}
},
async cancel(reason) {
try {
await reader.cancel(reason);
} finally {
release();
}
}
});
}
/**
* Configuration for connecting to the API.
*/
var ConnectionConfig = class ConnectionConfig {
static buildUserAgent() {
const userAgentParts = [`${ConnectionConfig.sdkUserAgentPrefix}${version}`];
if (ConnectionConfig.integration) userAgentParts.push(ConnectionConfig.integration);
return userAgentParts.join(" ");
}
/**
* Set the `User-Agent` on `headers`: an explicitly provided value always
* wins; otherwise the SDK-built one, tagged with the current integration.
*
* An SDK-built value carried over from an earlier config (configs are
* rebuilt via `new ConnectionConfig({ ...config })`) is recognized by its
* prefix and rebuilt, so it stays in sync with the current integration.
*/
static applyUserAgent(headers) {
const userAgent = headers["User-Agent"];
if (userAgent !== void 0 && !userAgent.startsWith(ConnectionConfig.sdkUserAgentPrefix)) return;
headers["User-Agent"] = ConnectionConfig.buildUserAgent();
}
/**
* Identify traffic from an integration wrapping the E2B SDK by appending
* `integration` (e.g. `'e2b-code-interpreter/0.1.0'`) to the `User-Agent`
* header of every request.
*
* Call once at startup, before any `ConnectionConfig` is constructed —
* configs read the value at construction time. Pass `undefined` to clear.
*
* @internal
* @hidden
* @hide
*/
static setIntegration(integration) {
ConnectionConfig.integration = integration;
}
constructor(opts) {
var _opts$debug, _opts$requestTimeoutM, _opts$retries, _opts$headers, _opts$apiHeaders;
_defineProperty(this, "debug", void 0);
_defineProperty(this, "domain", void 0);
_defineProperty(this, "apiUrl", void 0);
_defineProperty(this, "sandboxUrl", void 0);
_defineProperty(this, "logger", void 0);
_defineProperty(this, "requestTimeoutMs", void 0);
_defineProperty(this, "retries", void 0);
_defineProperty(this, "apiKey", void 0);
_defineProperty(this, "validateApiKey", void 0);
_defineProperty(this, "headers", void 0);
_defineProperty(this, "proxy", void 0);
_defineProperty(this, "httpVersion", void 0);
this.apiKey = (opts === null || opts === void 0 ? void 0 : opts.apiKey) || ConnectionConfig.apiKey;
this.validateApiKey = opts === null || opts === void 0 ? void 0 : opts.validateApiKey;
this.debug = (_opts$debug = opts === null || opts === void 0 ? void 0 : opts.debug) !== null && _opts$debug !== void 0 ? _opts$debug : ConnectionConfig.debug;
this.domain = (opts === null || opts === void 0 ? void 0 : opts.domain) || ConnectionConfig.domain;
this.requestTimeoutMs = (_opts$requestTimeoutM = opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs) !== null && _opts$requestTimeoutM !== void 0 ? _opts$requestTimeoutM : REQUEST_TIMEOUT_MS;
this.retries = resolveRetries((_opts$retries = opts === null || opts === void 0 ? void 0 : opts.retries) !== null && _opts$retries !== void 0 ? _opts$retries : 3);
this.logger = opts === null || opts === void 0 ? void 0 : opts.logger;
this.headers = _objectSpread2(_objectSpread2({}, (_opts$headers = opts === null || opts === void 0 ? void 0 : opts.headers) !== null && _opts$headers !== void 0 ? _opts$headers : {}), (_opts$apiHeaders = opts === null || opts === void 0 ? void 0 : opts.apiHeaders) !== null && _opts$apiHeaders !== void 0 ? _opts$apiHeaders : {});
ConnectionConfig.applyUserAgent(this.headers);
this.proxy = opts === null || opts === void 0 ? void 0 : opts.proxy;
this.httpVersion = resolveHttpVersion(opts === null || opts === void 0 ? void 0 : opts.httpVersion);
this.apiUrl = (opts === null || opts === void 0 ? void 0 : opts.apiUrl) || ConnectionConfig.apiUrl || (this.debug ? "http://localhost:3000" : `https://api.${this.domain}`);
this.sandboxUrl = (opts === null || opts === void 0 ? void 0 : opts.sandboxUrl) || ConnectionConfig.sandboxUrl;
}
/**
* Merge connection options bound to a class (e.g. by an `E2B` client) with
* the per-call options. Per-call options win, then the bound options, then
* the environment variables resolved by the `ConnectionConfig` constructor.
*
* Explicitly `undefined` per-call values are dropped so they fall back to the
* bound options instead of clearing them.
*
* @internal
* @hidden
* @hide
*/
static mergeOpts(boundOpts, opts) {
if (!boundOpts) return opts;
const merged = _objectSpread2({}, boundOpts);
for (const [key, value] of Object.entries(opts !== null && opts !== void 0 ? opts : {})) if (value !== void 0) Object.defineProperty(merged, key, {
value,
enumerable: true,
writable: true,
configurable: true
});
return merged;
}
static get domain() {
return getEnvVar("E2B_DOMAIN") || "e2b.app";
}
static get apiUrl() {
return getEnvVar("E2B_API_URL");
}
static get sandboxUrl() {
return getEnvVar("E2B_SANDBOX_URL");
}
static get debug() {
return (getEnvVar("E2B_DEBUG") || "false").toLowerCase() === "true";
}
static get apiKey() {
return getEnvVar("E2B_API_KEY");
}
getSignal(requestTimeoutMs, signal) {
return buildRequestSignal(requestTimeoutMs !== null && requestTimeoutMs !== void 0 ? requestTimeoutMs : this.requestTimeoutMs, signal);
}
getSandboxUrl(sandboxId, opts) {
var _opts$sandboxDomain;
if (this.sandboxUrl) return this.sandboxUrl;
if (this.debug) return `http://${this.getHost(sandboxId, opts.envdPort, opts.sandboxDomain)}`;
const sandboxDomain = (_opts$sandboxDomain = opts.sandboxDomain) !== null && _opts$sandboxDomain !== void 0 ? _opts$sandboxDomain : this.domain;
if (runtime !== "browser" && supportedDomains.includes(sandboxDomain)) return `https://sandbox.${sandboxDomain}`;
return `https://${this.getHost(sandboxId, opts.envdPort, sandboxDomain)}`;
}
getSandboxDirectUrl(sandboxId, opts) {
if (this.sandboxUrl) return this.sandboxUrl;
if (this.debug) return `http://${this.getHost(sandboxId, opts.envdPort, opts.sandboxDomain)}`;
return `https://${this.getHost(sandboxId, opts.envdPort, opts.sandboxDomain)}`;
}
getHost(sandboxId, port, sandboxDomain) {
if (this.debug) return `localhost:${port}`;
return `${port}-${sandboxId}.${sandboxDomain !== null && sandboxDomain !== void 0 ? sandboxDomain : this.domain}`;
}
};
_defineProperty(ConnectionConfig, "envdPort", 49983);
_defineProperty(ConnectionConfig, "integration", void 0);
_defineProperty(ConnectionConfig, "sdkUserAgentPrefix", "e2b-js-sdk/");
/**
* Base class for the resource classes (`Sandbox`, `Volume`, `Template`,
* `Secret`) whose static methods build a `ConnectionConfig` from per-call
* options. An {@link E2B} client exposes subclasses of these with its own
* options bound, and every static method resolves them through
* {@link ClientFactory.resolveOpts}.
*
* @internal
* @hidden
* @hide
*/
var ClientFactory = class {
/**
* Merge the connection options bound to this class with the per-call options,
* with the per-call options taking precedence.
*
* @internal
* @hidden
* @hide
*/
static resolveOpts(opts) {
return ConnectionConfig.mergeOpts(this.boundOpts, opts);
}
};
_defineProperty(ClientFactory, "boundOpts", void 0);
/**
* User used for the operation in the sandbox.
*/
const defaultUsername = "user";
//#endregion
//#region src/undici.ts
const POISONED_HTTP2_SESSION_RST = /NGHTTP2_(ENHANCE_YOUR_CALM|INTERNAL_ERROR)\b/;
/**
* Whether a `fetch` rejection is Node's HTTP/2 client resetting the stream
* itself because the session's memory budget is exhausted. The session stays
* unusable for every later request, so the dispatcher holding it must go.
*/
function isPoisonedHTTP2SessionError(err) {
if (!(err instanceof Error) || !(err.cause instanceof Error)) return false;
const cause = err.cause;
return cause.code === "ERR_HTTP2_STREAM_ERROR" && POISONED_HTTP2_SESSION_RST.test(cause.message);
}
const UNDICI_MIN_NODE = "22.19.0";
async function loadUndici(nodeVersion = (() => {
var _globalThis$process;
return (_globalThis$process = globalThis.process) === null || _globalThis$process === void 0 || (_globalThis$process = _globalThis$process.versions) === null || _globalThis$process === void 0 ? void 0 : _globalThis$process.node;
})()) {
if (!nodeVersion || (0, compare_versions.compareVersions)(nodeVersion, UNDICI_MIN_NODE) < 0) return;
try {
return await dynamicImport("undici");
} catch (_unused) {
return;
}
}
/**
* Late-bind the global fetch: runtimes and tools (msw, instrumentation) may
* replace `globalThis.fetch` after the SDK builds a fetcher. A factory rather
* than a shared const so per-proxy cache entries stay distinct closures.
*/
function lateBoundGlobalFetch() {
return ((input, init) => globalThis.fetch(input, init));
}
/**
* Create a fetch for the given runtime. Outside Node it late-binds the global
* fetch. On Node it lazily runs `build` on the first request and caches the
* built fetcher; a failed build is not cached, so the next request retries
* instead of replaying the same stale rejection forever.
*/
function createRuntimeFetch(currentRuntime, build) {
if (currentRuntime !== "node") return lateBoundGlobalFetch();
let fetcherPromise;
return (async (input, init) => {
var _fetcherPromise;
const promise = (_fetcherPromise = fetcherPromise) !== null && _fetcherPromise !== void 0 ? _fetcherPromise : fetcherPromise = build();
let fetcher;
try {
fetcher = await promise;
} catch (err) {
if (fetcherPromise === promise) fetcherPromise = void 0;
throw err;
}
return fetcher(input, init);
});
}
/**
* Build a fetch bound to a bounded undici dispatcher (HTTP/2 enabled unless
* `httpVersion` is `'1.1'`, `connections` origin connections, optional proxy
* tunnel). A dispatcher whose HTTP/2 session Node reports as out of memory is
* retired and replaced, so one poisoned session does not fail every later
* request. Falls back to the global fetch when undici cannot be loaded.
*/
async function buildDispatchedFetch(options) {
var _options$loadUndici, _options$httpVersion;
const undici = await ((_options$loadUndici = options.loadUndici) !== null && _options$loadUndici !== void 0 ? _options$loadUndici : loadUndici)();
if (!undici) return lateBoundGlobalFetch();
const { Agent, ProxyAgent, fetch: undiciFetch } = undici;
const agentOptions = {
allowH2: ((_options$httpVersion = options.httpVersion) !== null && _options$httpVersion !== void 0 ? _options$httpVersion : "2") === "2",
connections: options.connections
};
const createDispatcher = () => options.proxy ? new ProxyAgent(_objectSpread2(_objectSpread2({ uri: options.proxy }, agentOptions), {}, { proxyTunnel: true })) : new Agent(agentOptions);
const fetchWithDispatcher = undiciFetch;
let dispatcher = createDispatcher();
return (async (input, init) => {
const request = toUndiciRequestInput(input, init);
const used = dispatcher;
try {
return await fetchWithDispatcher(request.input, _objectSpread2(_objectSpread2({}, request.init), {}, { dispatcher: used }));
} catch (err) {
if (used === dispatcher && isPoisonedHTTP2SessionError(err)) {
dispatcher = createDispatcher();
used.close().catch(() => {});
}
throw err;
}
});
}
function toUndiciRequestInput(input, init) {
if (!isRequestLike(input)) return {
input,
init
};
const requestInit = _objectSpread2({
body: isReadableStreamLike(input.body) ? toDispatchableStream(input.body) : input.body,
cache: input.cache,
credentials: input.credentials,
headers: input.headers,
integrity: input.integrity,
keepalive: input.keepalive,
method: input.method,
mode: input.mode,
redirect: input.redirect,
referrer: input.referrer,
referrerPolicy: input.referrerPolicy,
signal: input.signal
}, init);
if (requestInit.body) requestInit.duplex = "half";
return {
input: input.url,
init: requestInit
};
}
//#endregion
//#region src/api/http2.ts
const DEFAULT_API_CONNECTION_LIMIT = 100;
const apiFetchers = /* @__PURE__ */ new Map();
function createApiFetch({ proxy, httpVersion = "2" } = {}) {
const key = `${httpVersion}:${proxy !== null && proxy !== void 0 ? proxy : ""}`;
const cached = apiFetchers.get(key);
if (cached) return cached;
const apiFetch = createApiFetchForRuntime(runtime, {
proxy,
httpVersion
});
apiFetchers.set(key, apiFetch);
return apiFetch;
}
function createApiFetchForRuntime(currentRuntime = runtime, options = {}) {
return createRuntimeFetch(currentRuntime, () => {
var _options$connectionLi;
return buildDispatchedFetch({
connections: (_options$connectionLi = options.connectionLimit) !== null && _options$connectionLi !== void 0 ? _options$connectionLi : getApiConnectionLimit(),
proxy: options.proxy,
httpVersion: options.httpVersion,
loadUndici: options.loadUndici
});
});
}
function getApiConnectionLimit() {
return parsePositiveIntEnv("E2B_API_CONNECTIONS", DEFAULT_API_CONNECTION_LIMIT);
}
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/asyncIterator.js
function _asyncIterator(r) {
var n, t, o, e = 2;
for ("undefined" != typeof Symbol && (t = Symbol.asyncIterator, o = Symbol.iterator); e--;) {
if (t && null != (n = r[t])) return n.call(r);
if (o && null != (n = r[o])) return new AsyncFromSyncIterator(n.call(r));
t = "@@asyncIterator", o = "@@iterator";
}
throw new TypeError("Object is not async iterable");
}
function AsyncFromSyncIterator(r) {
function AsyncFromSyncIteratorContinuation(r) {
if (Object(r) !== r) return Promise.reject(/* @__PURE__ */ new TypeError(r + " is not an object."));
var n = r.done;
return Promise.resolve(r.value).then(function(r) {
return {
value: r,
done: n
};
});
}
return AsyncFromSyncIterator = function AsyncFromSyncIterator(r) {
this.s = r, this.n = r.next;
}, AsyncFromSyncIterator.prototype = {
s: null,
n: null,
next: function next() {
return AsyncFromSyncIteratorContinuation(this.n.apply(this.s, arguments));
},
"return": function _return(r) {
var n = this.s["return"];
return void 0 === n ? Promise.resolve({
value: r,
done: !0
}) : AsyncFromSyncIteratorContinuation(n.apply(this.s, arguments));
},
"throw": function _throw(r) {
var n = this.s["return"];
return void 0 === n ? Promise.reject(r) : AsyncFromSyncIteratorContinuation(n.apply(this.s, arguments));
}
}, new AsyncFromSyncIterator(r);
}
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/OverloadYield.js
function _OverloadYield(e, d) {
this.v = e, this.k = d;
}
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/awaitAsyncGenerator.js
function _awaitAsyncGenerator(e) {
return new _OverloadYield(e, 0);
}
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/wrapAsyncGenerator.js
function _wrapAsyncGenerator(e) {
return function() {
return new AsyncGenerator(e.apply(this, arguments));
};
}
function AsyncGenerator(e) {
var r, t;
function resume(r, t) {
try {
var n = e[r](t), o = n.value, u = o instanceof _OverloadYield;
Promise.resolve(u ? o.v : o).then(function(t) {
if (u) {
var i = "return" === r ? "return" : "next";
if (!o.k || t.done) return resume(i, t);
t = e[i](t).value;
}
settle(n.done ? "return" : "normal", t);
}, function(e) {
resume("throw", e);
});
} catch (e) {
settle("throw", e);
}
}
function settle(e, n) {
switch (e) {
case "return":
r.resolve({
value: n,
done: !0
});
break;
case "throw":
r.reject(n);
break;
default: r.resolve({
value: n,
done: !1
});
}
(r = r.next) ? resume(r.key, r.arg) : t = null;
}
this._invoke = function(e, n) {
return new Promise(function(o, u) {
var i = {
key: e,
arg: n,
resolve: o,
reject: u,
next: null
};
t ? t = t.next = i : (r = t = i, resume(e, n));
});
}, "function" != typeof e["return"] && (this["return"] = void 0);
}
AsyncGenerator.prototype["function" == typeof Symbol && Symbol.asyncIterator || "@@asyncIterator"] = function() {
return this;
}, AsyncGenerator.prototype.next = function(e) {
return this._invoke("next", e);
}, AsyncGenerator.prototype["throw"] = function(e) {
return this._invoke("throw", e);
}, AsyncGenerator.prototype["return"] = function(e) {
return this._invoke("return", e);
};
//#endregion
//#region src/logs.ts
function formatLog(log) {
return JSON.parse(JSON.stringify(log, (_, value) => typeof value === "bigint" ? value.toString() : value));
}
function createRpcLogger(logger) {
function logEach(_x) {
return _logEach.apply(this, arguments);
}
function _logEach() {
_logEach = _wrapAsyncGenerator(function* (stream) {
var _iteratorAbruptCompletion = false;
var _didIteratorError = false;
var _iteratorError;
try {
for (var _iterator = _asyncIterator(stream), _step; _iteratorAbruptCompletion = !(_step = yield _awaitAsyncGenerator(_iterator.next())).done; _iteratorAbruptCompletion = false) {
const m = _step.value;
var _logger$debug;
(_logger$debug = logger.debug) === null || _logger$debug === void 0 || _logger$debug.call(logger, "Response stream:", formatLog(m));
yield m;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (_iteratorAbruptCompletion && _iterator.return != null) yield _awaitAsyncGenerator(_iterator.return());
} finally {
if (_didIteratorError) throw _iteratorError;
}
}
});
return _logEach.apply(this, arguments);
}
return (next) => async (req) => {
var _logger$info;
(_logger$info = logger.info) === null || _logger$info === void 0 || _logger$info.call(logger, `Request: POST ${req.url}`);
const res = await next(req);
if (res.stream) return _objectSpread2(_objectSpread2({}, res), {}, { message: logEach(res.message) });
else {
var _logger$info2;
(_logger$info2 = logger.info) === null || _logger$info2 === void 0 || _logger$info2.call(logger, "Response:", formatLog(res.message));
}
return res;
};
}
function createApiLogger(logger) {
return {
async onRequest({ request }) {
var _logger$info3;
(_logger$info3 = logger.info) === null || _logger$info3 === void 0 || _logger$info3.call(logger, `Request ${request.method} ${request.url}`);
return request;
},
async onResponse({ response }) {
if (response.status >= 400) {
var _logger$error;
(_logger$error = logger.error) === null || _logger$error === void 0 || _logger$error.call(logger, "Response:", response.status, response.statusText);
} else {
var _logger$info4;
(_logger$info4 = logger.info) === null || _logger$info4 === void 0 || _logger$info4.call(logger, "Response:", response.status, response.statusText);
}
return response;
}
};
}
//#endregion
//#region src/api/index.ts
/**
* Map an API error code and message to the matching error class — the same
* mapping {@link handleApiError} applies to HTTP responses, usable for error
* objects embedded in response bodies (e.g. per-fork results).
*/
function apiErrorFromCode(code, content, errorClass = SandboxError, stackTrace) {
if (code === 401) {
const message = "Unauthorized, please check your credentials.";
return new AuthenticationError(content ? `${message} - ${content}` : message);
}
if (code === 429) {
const message = "Rate limit exceeded, please try again later";
return new RateLimitError(content ? `${message} - ${content}` : message);
}
if (code === 503) {
const message = "Service temporarily unavailable, please retry";
return new ServiceBusyError(content ? `${message} - ${content}` : message);
}
const err = new errorClass(`${code}: ${content}`, stackTrace);
if (err instanceof SandboxError) err.statusCode = code;
return err;
}
function handleApiError(response, errorClass = SandboxError, stackTrace) {
var _response$error, _response$error3;
if (response.response.ok) return;
const status = response.response.status;
if (((_response$error = response.error) === null || _response$error === void 0 ? void 0 : _response$error.error_code) === GuestUnresponsiveError.code) return new GuestUnresponsiveError(`${response.error.message}. Resume the sandbox with \`onResume: 'reboot'\`.`);
if (status === 401 || status === 429 || status === 503) {
var _response$error$messa, _response$error2;
return apiErrorFromCode(status, (_response$error$messa = (_response$error2 = response.error) === null || _response$error2 === void 0 ? void 0 : _response$error2.message) !== null && _response$error$messa !== void 0 ? _response$error$messa : response.error, errorClass, stackTrace);
}
return apiErrorFromCode(status, ((_response$error3 = response.error) === null || _response$error3 === void 0 ? void 0 : _response$error3.message) || response.error || response.response.statusText, errorClass, stackTrace);
}
/**
* Client for interacting with the E2B API.
*/
var ApiClient = class {
constructor(config, opts = {}) {
var _config$headers, _opts$requireApiKey;
_defineProperty(this, "api", void 0);
const hasAuthorization = Object.keys((_config$headers = config.headers) !== null && _config$headers !== void 0 ? _config$headers : {}).some((name) => name.toLowerCase() === "authorization");
if (((_opts$requireApiKey = opts.requireApiKey) !== null && _opts$requireApiKey !== void 0 ? _opts$requireApiKey : true) && !config.apiKey && !hasAuthorization) throw new AuthenticationError("API key is required, please visit the API Keys tab at https://e2b.dev/dashboard?tab=keys to get your API key. You can either set the environment variable `E2B_API_KEY` or you can pass it directly to the sandbox like Sandbox.create({ apiKey: 'e2b_...' })");
this.api = (0, openapi_fetch.default)({
baseUrl: config.apiUrl,
fetch: withRetry(createApiFetch(config), config.retries, config.requestTimeoutMs),
headers: _objectSpread2(_objectSpread2(_objectSpread2({}, defaultHeaders), config.apiKey && { "X-API-KEY": config.apiKey }), config.headers),
querySerializer: { array: {
style: "form",
explode: false
} }
});
if (config.logger) this.api.use(createApiLogger(config.logger));
}
};
//#endregion
//#region src/sandbox/signature.ts
async function getSignature({ path, operation, user, expirationInSeconds, envdAccessToken }) {
if (!envdAccessToken) throw new Error("Access token is not set and signature cannot be generated!");
const signatureExpiration = expirationInSeconds != null ? Math.floor(Date.now() / 1e3) + expirationInSeconds : null;
let signatureRaw;
if (user == void 0) user = "";
if (signatureExpiration === null) signatureRaw = `${path}:${operation}:${user}:${envdAccessToken}`;
else signatureRaw = `${path}:${operation}:${user}:${envdAccessToken}:${signatureExpiration.toString()}`;
return {
signature: "v1_" + (await sha256(signatureRaw)).replace(/=+$/, ""),
expiration: signatureExpiration
};
}
//#endregion
//#region src/envd/http2.ts
const envdFetchers = /* @__PURE__ */ new Map();
const envdRpcFetchers = /* @__PURE__ */ new Map();
const envdUploadFetchers = /* @__PURE__ */ new Map();
const DEFAULT_ENVD_CONNECTION_LIMIT = 10;
const DEFAULT_ENVD_RPC_CONNECTION_LIMIT = 200;
const DEFAULT_ENVD_UPLOAD_CONNECTION_LIMIT = 50;
function createEnvdFetchForRuntime(currentRuntime = runtime, options = {}) {
return createRuntimeFetch(currentRuntime, () => {
var _options$connectionLi;
return buildDispatchedFetch({
connections: (_options$connectionLi = options.connectionLimit) !== null && _options$connectionLi !== void 0 ? _options$connectionLi : DEFAULT_ENVD_CONNECTION_LIMIT,
proxy: options.proxy,
httpVersion: options.httpVersion,
loadUndici: options.loadUndici
});
});
}
function fetcherKey(proxy, httpVersion) {
return `${httpVersion}:${proxy !== null && proxy !== void 0 ? proxy : ""}`;
}
function createEnvdFetch({ proxy, httpVersion = "2" } = {}) {
const key = fetcherKey(proxy, httpVersion);
const cached = envdFetchers.get(key);
if (cached) return cached;
const envdFetch = createEnvdFetchForRuntime(runtime, {
proxy,
httpVersion
});
envdFetchers.set(key, envdFetch);
return envdFetch;
}
function createEnvdRpcFetch({ proxy, httpVersion = "2" } = {}) {
const key = fetcherKey(proxy, httpVersion);
const cached = envdRpcFetchers.get(key);
if (cached) return cached;
const envdRpcFetch = createEnvdFetchForRuntime(runtime, {
connectionLimit: getEnvdRpcConnectionLimit(),
proxy,
httpVersion
});
envdRpcFetchers.set(key, envdRpcFetch);
return envdRpcFetch;
}
function getEnvdRpcConnectionLimit() {
return parsePositiveIntEnv("E2B_ENVD_RPC_CONNECTIONS", DEFAULT_ENVD_RPC_CONNECTION_LIMIT);
}
/**
* Fetch for `/files` uploads, always HTTP/1.1. Node's HTTP/2 client queues
* each buffered request body in full against the session's memory budget
* (`maxSessionMemory`, 10 MB) and resets whichever stream's response headers
* arrive while it is exhausted (ENHANCE_YOUR_CALM), so uploads multiplexed
* with everything else on the shared envd session fail each other and the
* reads next to them. HTTP/1.1 gives each upload a connection of its own; the
* pool is sized for many concurrent uploads per process since the request
* timeout keeps running while a request waits for a free connection.
*/
function createEnvdUploadFetch({ proxy } = {}) {
const key = fetcherKey(proxy, "1.1");
const cached = envdUploadFetchers.get(key);
if (cached) return cached;
const envdUploadFetch = createEnvdFetchForRuntime(runtime, {
connectionLimit: getEnvdUploadConnectionLimit(),
proxy,
httpVersion: "1.1"
});
envdUploadFetchers.set(key, envdUploadFetch);
return envdUploadFetch;
}
function getEnvdUploadConnectionLimit() {
return parsePositiveIntEnv("E2B_ENVD_UPLOAD_CONNECTIONS", DEFAULT_ENVD_UPLOAD_CONNECTION_LIMIT);
}
const ENVD_DEBUG_FALLBACK = "99.99.99";
const ENVD_ENVD_CLOSE = "0.5.2";
const ENVD_OCTET_STREAM_UPLOAD = "0.5.7";
//#endregion
//#region src/envd/process/process_pb.ts
/**
* Describes the file process/process.proto.
*/
const file_process_process = /*@__PURE__*/ (0, _bufbuild_protobuf_codegenv2.fileDesc)("ChVwcm9jZXNzL3Byb2Nlc3MucHJvdG8SB3Byb2Nlc3MiSgoDUFRZEh8KBHNpemUYASABKAsyES5wcm9jZXNzLlBUWS5TaXplGiIKBFNpemUSDAoEY29scxgBIAEoDRIMCgRyb3dzGAIgASgNIqEBCg1Qcm9jZXNzQ29uZmlnEgsKA2NtZBgBIAEoCRIMCgRhcmdzGAIgAygJEi4KBGVudnMYAyADKAsyIC5wcm9jZXNzLlByb2Nlc3NDb25maWcuRW52c0VudHJ5EhAKA2N3ZBgEIAEoCUgAiAEBGisKCUVudnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQgYKBF9jd2QiDQoLTGlzdFJlcXVlc3QiXAoLUHJvY2Vzc0luZm8SJgoGY29uZmlnGAEgASgLMhYucHJvY2Vzcy5Qcm9jZXNzQ29uZmlnEgsKA3BpZBgCIAEoDRIQCgN0YWcYAyABKAlIAIgBAUIGCgRfdGFnIjcKDExpc3RSZXNwb25zZRInCglwcm9jZXNzZXMYASADKAsyFC5wcm9jZXNzLlByb2Nlc3NJbmZvIpcBCgxTdGFydFJlcXVlc3QSJwoHcHJvY2VzcxgBIAEoCzIWLnByb2Nlc3MuUHJvY2Vzc0NvbmZpZxIeCgNwdHkYAiABKAsyDC5wcm9jZXNzLlBUWUgAiAEBEhAKA3RhZxgDIAEoCUgBiAEBEhIKBXN0ZGluGAQgASgISAKIAQFCBgoEX3B0eUIGCgRfdGFnQggKBl9zdGRpbiJiCg1VcGRhdGVSZXF1ZXN0EikKB3Byb2Nlc3MYASABKAsyGC5wcm9jZXNzLlByb2Nlc3NTZWxlY3RvchIeCgNwdHkYAiABKAsyDC5wcm9jZXNzLlBUWUgAiAEBQgYKBF9wdHkiEAoOVXBkYXRlUmVzcG9uc2UirwMKDFByb2Nlc3NFdmVudBIxCgVzdGFydBgBIAEoCzIgLnByb2Nlc3MuUHJvY2Vzc0V2ZW50LlN0YXJ0RXZlbnRIABIvCgRkYXRhGAIgASgLMh8ucHJvY2Vzcy5Qcm9jZXNzRXZlbnQuRGF0YUV2ZW50SAASLQoDZW5kGAMgASgLMh4ucHJvY2Vzcy5Qcm9jZXNzRXZlbnQuRW5kRXZlbnRIABI0CglrZWVwYWxpdmUYBCABKAsyHy5wcm9jZXNzLlByb2Nlc3NFdmVudC5LZWVwQWxpdmVIABoZCgpTdGFydEV2ZW50EgsKA3BpZBgBIAEoDRpICglEYXRhRXZlbnQSEAoGc3Rkb3V0GAEgASgMSAASEAoGc3RkZXJyGAIgASgMSAASDQoDcHR5GAMgASgMSABCCAoGb3V0cHV0GlsKCEVuZEV2ZW50EhEKCWV4aXRfY29kZRgBIAEoERIOCgZleGl0ZWQYAiABKAgSDgoGc3RhdHVzGAMgASgJEhIKBWVycm9yGAQgASgJSACIAQFCCAoGX2Vycm9yGgsKCUtlZXBBbGl2ZUIHCgVldmVudCI1Cg1TdGFydFJlc3BvbnNlEiQKBWV2ZW50GAEgASgLMhUucHJvY2Vzcy5Qcm9jZXNzRXZlbnQiNwoPQ29ubmVjdFJlc3BvbnNlEiQKBWV2ZW50GAEgASgLMhUucHJvY2Vzcy5Qcm9jZXNzRXZlbnQiYwoQU2VuZElucHV0UmVxdWVzdBIpCgdwcm9jZXNzGAEgASgLMhgucHJvY2Vzcy5Qcm9jZXNzU2VsZWN0b3ISJAoFaW5wdXQYAiABKAsyFS5wcm9jZXNzLlByb2Nlc3NJbnB1dCITChFTZW5kSW5wdXRSZXNwb25zZSI3CgxQcm9jZXNzSW5wdXQSDwoFc3RkaW4YASABKAxIABINCgNwdHkYAiABKAxIAEIHCgVpbnB1dCLCAgoSU3RyZWFtSW5wdXRSZXF1ZXN0EjcKBXN0YXJ0GAEgASgLMiYucHJvY2Vzcy5TdHJlYW1JbnB1dFJlcXVlc3QuU3RhcnRFdmVudEgAEjUKBGRhdGEYAiABKAsyJS5wcm9jZXNzLlN0cmVhbUlucHV0UmVxdWVzdC5EYXRhRXZlbnRIABI6CglrZWVwYWxpdmUYAyABKAsyJS5wcm9jZXNzLlN0cmVhbUlucHV0UmVxdWVzdC5LZWVwQWxpdmVIABo3CgpTdGFydEV2ZW50EikKB3Byb2Nlc3MYASABKAsyGC5wcm9jZXNzLlByb2Nlc3NTZWxlY3RvchoxCglEYXRhRXZlbnQSJAoFaW5wdXQYAiABKAsyFS5wcm9jZXNzLlByb2Nlc3NJbnB1dBoLCglLZWVwQWxpdmVCBwoFZXZlbnQiFQoTU3RyZWFtSW5wdXRSZXNwb25zZSJfChFTZW5kU2lnbmFsUmVxdWVzdBIpCgdwcm9jZXNzGAEgASgLMhgucHJvY2Vzcy5Qcm9jZXNzU2VsZWN0b3ISHwoGc2lnbmFsGAIgASgOMg8ucHJvY2Vzcy5TaWduYWwiFAoSU2VuZFNpZ25hbFJlc3BvbnNlIj4KEUNsb3NlU3RkaW5SZXF1ZXN0EikKB3Byb2Nlc3MYASABKAsyGC5wcm9jZXNzLlByb2Nlc3NTZWxlY3RvciIUChJDbG9zZVN0ZGluUmVzcG9uc2UiOwoOQ29ubmVjdFJlcXVlc3QSKQoHcHJvY2VzcxgBIAEoCzIYLnByb2Nlc3MuUHJvY2Vzc1NlbGVjdG9yIjsKD1Byb2Nlc3NTZWxlY3RvchINCgNwaWQYASABKA1IABINCgN0YWcYAiABKAlIAEIKCghzZWxlY3RvcipICgZTaWduYWwSFgoSU0lHTkFMX1VOU1BFQ0lGSUVEEAASEgoOU0lHTkFMX1NJR1RFUk0QDxISCg5TSUdOQUxfU0lHS0lMTBAJMpEECgdQcm9jZXNzEjMKBExpc3QSFC5wcm9jZXNzLkxpc3RSZXF1ZXN0GhUucHJvY2Vzcy5MaXN0UmVzcG9uc2USPgoHQ29ubmVjdBIXLnByb2Nlc3MuQ29ubmVjdFJlcXVlc3QaGC5wcm9jZXNzLkNvbm5lY3RSZXNwb25zZTABEjgKBVN0YXJ0EhUucHJvY2Vzcy5TdGFydFJlcXVlc3QaFi5wcm9jZXNzLlN0YXJ0UmVzcG9uc2UwARI5CgZVcGRhdGUSFi5wcm9jZXNzLlVwZGF0ZVJlcXVlc3QaFy5wcm9jZXNzLlVwZGF0ZVJlc3BvbnNlEkoKC1N0cmVhbUlucHV0EhsucHJvY2Vzcy5TdHJlYW1JbnB1dFJlcXVlc3QaHC5wcm9jZXNzLlN0cmVhbUlucHV0UmVzcG9uc2UoARJCCglTZW5kSW5wdXQSGS5wcm9jZXNzLlNlbmRJbnB1dFJlcXVlc3QaGi5wcm9jZXNzLlNlbmRJbnB1dFJlc3BvbnNlEkUKClNlbmRTaWduYWwSGi5wcm9jZXNzLlNlbmRTaWduYWxSZXF1ZXN0GhsucHJvY2Vzcy5TZW5kU2lnbmFsUmVzcG9uc2USRQoKQ2xvc2VTdGRpbhIaLnByb2Nlc3MuQ2xvc2VTdGRpblJlcXVlc3QaGy5wcm9jZXNzLkNsb3NlU3RkaW5SZXNwb25zZUJXCgtjb20ucHJvY2Vzc0IMUHJvY2Vzc1Byb3RvUAGiAgNQWFiqAgdQcm9jZXNzygIHUHJvY2Vzc+ICE1Byb2Nlc3NcR1BCTWV0YWRhdGHqAgdQcm9jZXNzYgZwcm90bzM");
/**
* Describes the message process.StreamInputRequest.
* Use `create(StreamInputRequestSchema)` to create a new message.
*/
const StreamInputRequestSchema = /*@__PURE__*/ (0, _bufbuild_protobuf_codegenv2.messageDesc)(file_process_process, 14);
/**
* @generated from service process.Process
*/
const Process = /*@__PURE__*/ (0, _bufbuild_protobuf_codegenv2.serviceDesc)(file_process_process, 0);
//#endregion
//#region src/envd/rpc.ts
/**
* Message fragments different JS runtimes use when the connection to the sandbox
* is dropped mid-request. The transport surfaces a dropped connection (e.g. an
* HTTP/2 stream reset) with runtime- and version-specific wording, so we match
* every known variant:
* - Node (undici): `terminated`
* - Bun: `The socket connection was closed unexpectedly`
* - Deno: `error reading a body from connection`
* - Cloudflare Workers: `Network connection lost`
* - Browser: `network error`
*/
const CONNECTION_TERMINATED_MESSAGES = [
"terminated",
"The socket connection was closed unexpectedly",
"error reading a body from connection",
"Network connection lost",
"network error"
];
/**
* Checks whether a message matches any known runtime variant of the connection to
* the sandbox being dropped mid-request (see {@link CONNECTION_TERMINATED_MESSAGES}).
*/
function isConnectionTerminatedMessage(message) {
if (!message) return false;
return CONNECTION_TERMINATED_MESSAGES.some((fragment) => message.includes(fragment));
}
/**
* Checks whether the error is the signature of the connection to the sandbox being
* dropped mid-request — an HTTP/2 stream reset surfaced by connect as `Code.Unknown`
* with one of the runtime-specific connection-dropped messages.
*/
function isConnectionTerminatedError(err) {
return err instanceof _connectrpc_connect.ConnectError && err.code === _connectrpc_connect.Code.Unknown && isConnectionTerminatedMessage(err.rawMessage);
}
/**
* Checks whether the error is a transport-level failure of the request to the sandbox:
* the connection could not be established or failed with a browser's opaque network
* error (connect wraps the `fetch` rejection as `Code.Unknown` with the failure as
* `cause`) or was dropped mid-request. Errors
* envd responded with are never transport failures.
*/
function isTransportFailure(err) {
if (isConnectionTerminatedError(err)) return true;
return err instanceof _connectrpc_connect.ConnectError && err.code === _connectrpc_connect.Code.Unknown && isNetworkError(err.cause);
}
/**
* Checks whether the error is an `Unavailable` whose message the proxy's answers do
* not explain — neither the sandbox not found nor its port not open — e.g. envd
* ending a stream with "the connection to sandbox ... ended before the stream
* completed" when the sandbox is killed mid-command. Whether the sandbox is gone
* can only be told by probing it.
*/
function isAmbiguousUnavailable(err) {
return err instanceof _connectrpc_connect.ConnectError && err.code === _connectrpc_connect.Code.Unavailable && !isSandboxNotFoundMessage(err.rawMessage) && !isSandboxPortNotOpenMessage(err.rawMessage);
}
/**
* Builds the error for a request that failed at the connection level when the
* follow-up health probe failed too. A probe answered by the proxy with the sandbox
* running but envd's port not open is already a `SandboxUnreachableError` and is
* kept, with the failed request as its cause; otherwise the probe's failure is
* reported alongside the request's, which becomes the cause.
*/
function formatSandboxUnreachableError(err, probeErr) {
if (probeErr instanceof SandboxUnreachableError) {
probeErr.cause = err;
return probeErr;
}
const probeMessage = probeErr instanceof Error ? probeErr.message : String(probeErr);
return new SandboxUnreachableError(`${err.message}: The sandbox could not be reached and its health probe failed too (${probeMessage}). It was not confirmed to be stopped — this is likely a network issue or envd inside the sandbox not being up yet; check the sandbox state with 'Sandbox.getInfo()'.`, { cause: err });
}
/**
* Runs the health probe for a request that failed at the connection level and
* returns the typed error for the outcome: a `SandboxNotRunningError` when the
* sandbox is confirmed gone, a `SandboxUnreachableError` when the probe got no
* answer either, or `undefined` when the sandbox answered (the failure was
* transient and the original error applies).
*/
async function resolveTransportFailure(err, checkHealth) {
let running;
try {
running = await checkHealth();
} catch (probeErr) {
return formatSandboxUnreachableError(err, probeErr);
}
if (running === false) return new SandboxNotRunningError(`${err.message}: The sandbox was killed or reached its end of life while the request was in flight.`, { cause: err });
}
const DEFAULT_ERROR_MAP$1 = {
[_connectrpc_connect.Code.InvalidArgument]: (message) => new InvalidArgumentError(message),
[_connectrpc_connect.Code.Unauthenticated]: (message) => new AuthenticationError(message),
[_connectrpc_connect.Code.NotFound]: (message) => new NotFoundError(message),
[_connectrpc_connect.Code.ResourceExhausted]: (message) => new RateLimitError(`${message}: Rate limit exceeded, please try again later.`),
[_connectrpc_connect.Code.Unavailable]: formatSandboxUnavailableError,
[_connectrpc_connect.Code.Canceled]: (message) => new TimeoutError(`${message}: This error is likely due to exceeding 'requestTimeoutMs'. You can pass the request timeout value as an option when making the request.`),
[_connectrpc_connect.Code.DeadlineExceeded]: (message) => new TimeoutError(`${message}: This error is likely due to exceeding 'timeoutMs' — the total time a long running request (like command execution or directory watch) can be active. It can be modified by passing 'timeoutMs' when making the request. Use '0' to disable the timeout.`)
};
/**
* Handles errors from envd RPC calls by mapping gRPC status codes to specific error types.
*
* @param err - The caught error, expected to be a `ConnectError` from the gRPC transport.
* @param errorMap - Optional map of gRPC `Code` values to error factory functions that override the defaults.
* @returns The corresponding `Error` instance mapped from the gRPC status code, or the original error if it is not a `ConnectError`.
*/
function handleRpcError(err, errorMap) {
if (err instanceof _connectrpc_connect.ConnectError) {
if (errorMap && err.code in errorMap) return errorMap[err.code](err.message);
if (err.code in DEFAULT_ERROR_MAP$1) return DEFAULT_ERROR_MAP$1[err.code](err.message);
return new SandboxError(`${err.code}: ${err.message}`);
}
return err;
}
/**
* Like {@link handleRpcError}, but when the request failed at the connection level
* (the connection to the sandbox could not be established or was dropped
* mid-request), or envd/the proxy answered an `Unavailable` that does not say
* whether the sandbox is gone ({@link isAmbiguousUnavailable}), it probes the
* sandbox health to tell apart the sandbox being killed from the sandbox being
* unreachable or a transient network failure (e.g. a load balancer dropping the
* connection). When the probe confirms the sandbox is gone, a
* `SandboxNotRunningError` is returned — the same error a request answered by the
* proxy with the sandbox not found gets; when the probe gets no answer either, a
* `SandboxUnreachableError`; otherwise the error maps as usual (an ambiguous
* `Unavailable` to a `SandboxUnreachableError` with the sandbox state unknown).
*
* @param err - The caught error, expected to be a `ConnectError` from the gRPC transport.
* @param checkHealth - Probe resolving to whether the sandbox is running (`undefined` when unknown) and rejecting when it cannot be reached.
* @param errorMap - Optional map of gRPC `Code` values to error factory functions that override the defaults.
* @returns The corresponding `Error` instance.
*/
async function handleRpcErrorWithHealthCheck(err, checkHealth, errorMap) {
if (checkHealth && (isTransportFailure(err) || isAmbiguousUnavailable(err))) {
const resolved = await resolveTransportFailure(err, checkHealth);
if (resolved) return resolved;
}
return handleRpcError(err, errorMap);
}
/**
* Kills the process with the given PID with `SIGKILL`.
*
* @returns `true` if the process was killed, `false` if it was not found.
*/
async function killProcess(rpc, pid, signal, checkHealth) {
try {
await rpc.sendSignal({
process: { selector: {
case: "pid",
value: pid
} },
signal: 9
}, { signal });
return true;
} catch (err) {
if (err instanceof _connectrpc_connect.ConnectError && err.code === _connectrpc_connect.Code.NotFound) return false;
throw await handleRpcErrorWithHealthCheck(err, checkHealth);
}
}
function encode64(value) {
switch (runtime) {
case "deno": return btoa(value);
case "node": return Buffer.from(value).toString("base64");
case "bun": return Buffer.from(value).toString("base64");
default: return btoa(value);
}
}
function authenticationHeader(envdVersion, username) {
if (username == void 0 && (0, compare_versions.compareVersions)(envdVersion, "0.4.0") < 0) username = defaultUsername;
if (!username) return {};
return { Authorization: `Basic ${encode64(`${username}:`)}` };
}
/**
* Rejects a plain (non-Connect-encoded) 502 answered by the proxy in front of envd —
* the sandbox is gone or envd's port is not open — as a `ConnectError` carrying the
* proxy's message. connect reads that message for unary RPCs only; a streaming RPC
* would otherwise surface it as a bare `HTTP 502`.
*/
async function rejectProxyUnavailableResponse(res) {
if (res.status !== 502) return res;
let message = "";
try {
message = await res.text();
} catch (_unused) {}
try {
const body = JSON.parse(message);
if (typeof (body === null || body === void 0 ? void 0 : body.code) === "string" && (0, _connectrpc_connect_protocol_connect.codeFromString)(body.code) !== void 0) return new Response(message, {
status: res.status,
statusText: res.statusText,
headers: res.headers
});
if (typeof (body === null || body === void 0 ? void 0 : body.message) === "string") message = body.message;
} catch (_unused2) {}
throw new _connectrpc_connect.ConnectError(message || `HTTP ${res.status}`, _connectrpc_connect.Code.Unavailable);
}
//#endregion
//#region src/envd/api.ts
const DEFAULT_ERROR_MAP = {
400: (message) => new InvalidArgumentError(message),
401: (message) => new AuthenticationError(message),
404: (message) => new NotFoundError(message),
429: (message) => new RateLimitError(`${message}: The requests are being rate limited.`),
502: formatSandboxUnavailableError,
507: (message) => new NotEnoughSpaceError(message)
};
const HEALTH_CHECK_TIMEOUT_MS = 5e3;
/**
* Probes the sandbox's envd health endpoint.
*
* @param envdApi - The envd API client of the sandbox.
* @returns `true` if the sandbox is running, `false` if it is not, `undefined` if it answered but its state could not be determined.
* @throws The `fetch` failure when the sandbox could not be reached (no answer within the probe timeout), or the `SandboxUnreachableError` a request would get when the proxy reports the sandbox running but envd's port not open.
*/
async function checkSandboxHealth(envdApi) {
const res = await envdApi.api.GET("/health", { signal: AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS) });
if (res.response.status === 502) {
const err = formatSandboxUnavailableError(await getEnvdApiErrorMessage(res));
if (err instanceof SandboxNotRunningError) return false;
throw err;
}
if (res.response.ok) return true;
}
/**
* Checks whether a `fetch` rejection is a transport-level failure of the request to
* the sandbox: the connection could not be established or was dropped mid-request
* (the latter surfaces with runtime-specific wording, e.g. undici's `terminated`).
*/
function isFetchTransportFailure(err) {
return err instanceof Error && (isConnectionTerminatedMessage(err.message) || isNetworkError(err));
}
/**
* Handles transport-level fetch failures from envd API calls. When the connection to
* the sandbox could not be established or was dropped mid-request, probes the sandbox
* health to tell apart the sandbox being killed from the sandbox being unreachable or
* a transient network failure (e.g. a load balancer dropping the connection).
*
* @param err - The caught error, expected to be a fetch transport failure.
* @param checkHealth - Probe resolving to whether the sandbox is running (`undefined` when unknown) and rejecting when it cannot be reached.
* @returns A `SandboxNotRunningError` when the sandbox is confirmed gone, a `SandboxUnreachableError` when the probe got no answer either, or the original error otherwise.
*/
async function handleEnvdApiFetchError(err, checkHealth) {
if (checkHealth && isFetchTransportFailure(err)) {
const resolved = await resolveTransportFailure(err, checkHealth);
if (resolved) return resolved;
}
return err;
}
/**
* Extracts the error message of a non-2xx envd API response: the `message` of its
* JSON body, else its text body, else its status text.
*/
async function getEnvdApiErrorMessage(res) {
var _ref, _res$error;
let message = (_ref = typeof res.error === "string" ? res.error : (_res$error = res.error) === null || _res$error === void 0 ? void 0 : _res$error.message) !== null && _ref !== void 0 ? _ref : "";
if (!message && !res.response.bodyUsed) try {
message = await res.response.text();
} catch (_unused) {}
return message || res.response.statusText;
}
/**
* Handles errors from envd API responses by mapping HTTP status codes to specific error types.
*
* @param res - The API response object containing an optional error and the raw `Response`.
* @param errorMap - Optional map of HTTP status codes to error factory functions that override the defaults.
* @returns The corresponding `Error` instance if an error is present, or `undefined` if the response is successful.
*/
async function handleEnvdApiError(res, errorMap) {
if (res.response.ok) return;
const message = await getEnvdApiErrorMessage(res);
if (errorMap && res.response.status in errorMap) {
var _errorMap$res$respons;
return (_errorMap$res$respons = errorMap[res.response.status]) === null || _errorMap$res$respons === void 0 ? void 0 : _errorMap$res$respons.call(errorMap, message);
}
if (res.response.status in DEFAULT_ERROR_MAP) {
var _DEFAULT_ERROR_MAP$re;
return (_DEFAULT_ERROR_MAP$re = DEFAULT_ERROR_MAP[res.response.status]) === null || _DEFAULT_ERROR_MAP$re === void 0 ? void 0 : _DEFAULT_ERROR_MAP$re.call(DEFAULT_ERROR_MAP, message);
}
return new SandboxError(`${res.response.status}: ${message}`);
}
async function handleProcessStartEvent(events) {
var _startEvent$event;
const startEvent = (await events[Symbol.asyncIterator]().next()).value;
if (((_startEvent$event = startEvent.event) === null || _startEvent$event === void 0 ? void 0 : _startEvent$event.event.case) !== "start") throw new Error("Expected start event");
return startEvent.event.event.value.pid;
}
async function handleWatchDirStartEvent(events) {
var _startEvent$event2;
const startEvent = (await events[Symbol.asyncIterator]().next()).value;
if (((_startEvent$event2 = startEvent.event) === null || _startEvent$event2 === void 0 ? void 0 : _startEvent$event2.case) !== "start") throw new Error("Expected start event");
return startEvent.event.value;
}
var EnvdApiClient = class {
constructor(config, metadata) {
_defineProperty(this, "api", void 0);
_defineProperty(this, "version", void 0);
this.api = (0, openapi_fetch.default)({
baseUrl: config.apiUrl,
fetch: config === null || config === void 0 ? void 0 : config.fetch,
headers: _objectSpread2(_objectSpread2({}, config === null || config === void 0 ? void 0 : config.headers), config.envdAccessToken && { "X-Access-Token": config.envdAccessToken })
});
this.version = metadata.version;
if (config.logger) this.api.use(createApiLogger(config.logger));
}
};
//#endregion
//#region src/envd/filesystem/filesystem_pb.ts
/**
* Describes the file filesystem/filesystem.proto.
*/
const file_filesystem_filesystem = /*@__PURE__*/ (0, _bufbuild_protobuf_codegenv2.fileDesc)("ChtmaWxlc3lzdGVtL2ZpbGVzeXN0ZW0ucHJvdG8SCmZpbGVzeXN0ZW0iMgoLTW92ZVJlcXVlc3QSDgoGc291cmNlGAEgASgJEhMKC2Rlc3RpbmF0aW9uGAIgASgJIjQKDE1vdmVSZXNwb25zZRIkCgVlbnRyeRgBIAEoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIh4KDk1ha2VEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiNwoPTWFrZURpclJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHQoNUmVtb3ZlUmVxdWVzdBIMCgRwYXRoGAEgASgJIhAKDlJlbW92ZVJlc3BvbnNlIhsKC1N0YXRSZXF1ZXN0EgwKBHBhdGgYASABKAkiNAoMU3RhdFJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8i+QIKCUVudHJ5SW5mbxIMCgRuYW1lGAEgASgJEiIKBHR5cGUYAiABKA4yFC5maWxlc3lzdGVtLkZpbGVUeXBlEgwKBHBhdGgYAyABKAkSDAoEc2l6ZRgEIAEoAxIMCgRtb2RlGAUgASgNEhMKC3Blcm1pc3Npb25zGAYgASgJEg0KBW93bmVyGAcgASgJEg0KBWdyb3VwGAggASgJEjEKDW1vZGlmaWVkX3RpbWUYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhsKDnN5bWxpbmtfdGFyZ2V0GAogASgJSACIAQESNQoIbWV0YWRhdGEYCyADKAsyIy5maWxlc3lzdGVtLkVudHJ5SW5mby5NZXRhZGF0YUVudHJ5EhIKCmlzX3N5bWxpbmsYDCABKAgaLwoNTWV0YWRhdGFFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQhEKD19zeW1saW5rX3RhcmdldCItCg5MaXN0RGlyUmVxdWVzdBIMCgRwYXRoGAEgASgJEg0KBWRlcHRoGAIgASgNIjkKD0xpc3REaXJSZXNwb25zZRImCgdlbnRyaWVzGAEgAygLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iZwoPV2F0Y2hEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIEhUKDWluY2x1ZGVfZW50cnkYAyABKAgSHAoUYWxsb3dfbmV0d29ya19tb3VudHMYBCABKAgieQoPRmlsZXN5c3RlbUV2ZW50EgwKBG5hbWUYASABKAkSIwoEdHlwZRgCIAEoDjIVLmZpbGVzeXN0ZW0uRXZlbnRUeXBlEikKBWVudHJ5GAMgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm9IAIgBAUIICgZfZW50cnki4AEKEFdhdGNoRGlyUmVzcG9uc2USOAoFc3RhcnQYASABKAsyJy5maWxlc3lzdGVtLldhdGNoRGlyUmVzcG9uc2UuU3RhcnRFdmVudEgAEjEKCmZpbGVzeXN0ZW0YAiABKAsyGy5maWxlc3lzdGVtLkZpbGVzeXN0ZW1FdmVudEgAEjsKCWtlZXBhbGl2ZRgDIAEoCzImLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXNwb25zZS5LZWVwQWxpdmVIABoMCgpTdGFydEV2ZW50GgsKCUtlZXBBbGl2ZUIHCgVldmVudCJsChRDcmVhdGVXYXRjaGVyUmVxdWVzdBIMCgRwYXRoGAEgASgJEhEKCXJlY3Vyc2l2ZRgCIAEoCBIVCg1pbmNsdWRlX2VudHJ5GAMgASgIEhwKFGFsbG93X25ldHdvcmtfbW91bnRzGAQgASgIIisKFUNyZWF0ZVdhdGNoZXJSZXNwb25zZRISCgp3YXRjaGVyX2lkGAEgASgJIi0KF0dldFdhdGNoZXJFdmVudHNSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiRwoYR2V0V2F0Y2hlckV2ZW50c1Jlc3BvbnNlEisKBmV2ZW50cxgBIAMoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50IioKFFJlbW92ZVdhdGNoZXJSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiFwoVUmVtb3ZlV2F0Y2hlclJlc3BvbnNlKmkKCEZpbGVUeXBlEhkKFUZJTEVfVFlQRV9VTlNQRUNJRklFRBAAEhIKDkZJTEVfVFlQRV9GSUxFEAESFwoTRklMRV9UWVBFX0RJUkVDVE9SWRACEhUKEUZJTEVfVFlQRV9TWU1MSU5LEAMqmAEKCUV2ZW50VHlwZRIaChZFVkVOVF9UWVBFX1VOU1BFQ0lGSUVEEAASFQoRRVZFTlRfVFlQRV9DUkVBVEUQARIUChBFVkVOVF9UWVBFX1dSSVRFEAISFQoRRVZFTlRfVFlQRV9SRU1PVkUQAxIVChFFVkVOVF9UWVBFX1JFTkFNRRAEEhQKEEVWRU5UX1RZUEVfQ0hNT0QQBTKfBQoKRmlsZXN5c3RlbRI5CgRTdGF0EhcuZmlsZXN5c3RlbS5TdGF0UmVxdWVzdBoYLmZpbGVzeXN0ZW0uU3RhdFJlc3BvbnNlEkIKB01ha2VEaXISGi5maWxlc3lzdGVtLk1ha2VEaXJSZXF1ZXN0GhsuZmlsZXN5c3RlbS5NYWtlRGlyUmVzcG9uc2USOQoETW92ZRIXLmZpbGVzeXN0ZW0uTW92ZVJlcXVlc3QaGC5maWxlc3lzdGVtLk1vdmVSZXNwb25zZRJCCgdMaXN0RGlyEhouZmlsZXN5c3RlbS5MaXN0RGlyUmVxdWVzdBobLmZpbGVzeXN0ZW0uTGlzdERpclJlc3BvbnNlEj8KBlJlbW92ZRIZLmZpbGVzeXN0ZW0uUmVtb3ZlUmVxdWVzdBoaLmZpbGVzeXN0ZW0uUmVtb3ZlUmVzcG9uc2USRwoIV2F0Y2hEaXISGy5maWxlc3lzdGVtLldhdGNoRGlyUmVxdWVzdBocLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXNwb25zZTABElQKDUNyZWF0ZVdhdGNoZXISIC5maWxlc3lzdGVtLkNyZWF0ZVdhdGNoZXJSZXF1ZXN0GiEuZmlsZXN5c3RlbS5DcmVhdGVXYXRjaGVyUmVzcG9uc2USXQoQR2V0V2F0Y2hlckV2ZW50cxIjLmZpbGVzeXN0ZW0uR2V0V2F0Y2hlckV2ZW50c1JlcXVlc3QaJC5maWxlc3lzdGVtLkdldFdhdGNoZXJFdmVudHNSZXNwb25zZRJUCg1SZW1vdmVXYXRjaGVyEiAuZmlsZXN5c3RlbS5SZW1vdmVXYXRjaGVyUmVxdWVzdBohLmZpbGVzeXN0ZW0uUmVtb3ZlV2F0Y2hlclJlc3BvbnNlQmkKDmNvbS5maWxlc3lzdGVtQg9GaWxlc3lzdGVtUHJvdG9QAaICA0ZYWKoCCkZpbGVzeXN0ZW3KAgpGaWxlc3lzdGVt4gIWRmlsZXN5c3RlbVxHUEJNZXRhZGF0YeoCCkZpbGVzeXN0ZW1iBnByb3RvMw", [_bufbuild_protobuf_wkt.file_google_protobuf_timestamp]);
/**
* @generated from service filesystem.Filesystem
*/
const Filesystem$1 = /*@__PURE__*/ (0, _bufbuild_protobuf_codegenv2.serviceDesc)(file_filesystem_filesystem, 0);
//#endregion
//#region src/sandbox/filesystem/watchHandle.ts
/**
* Sandbox filesystem event types.
*/
let FilesystemEventType = /* @__PURE__ */ function(FilesystemEventType) {
/**
* Filesystem object permissions were changed.
*/
FilesystemEventType["CHMOD"] = "chmod";
/**
* Filesystem object was created.
*/
FilesystemEventType["CREATE"] = "create";
/**
* Filesystem object was removed.
*/
FilesystemEventType["REMOVE"] = "remove";
/**
* Filesystem object was renamed.
*/
FilesystemEventType["RENAME"] = "rename";
/**
* Filesystem object was written to.
*/
FilesystemEventType["WRITE"] = "write";
return FilesystemEventType;
}({});
function mapEventType(type) {
switch (type) {
case 5: return "chmod";
case 1: return "create";
case 3: return "remove";
case 4: return "rename";
case 2: return "write";
}
}
/**
* Handle for watching a directory in the sandbox filesystem.
*
* Use {@link WatchHandle.stop} to stop watching the directory.
*/
var WatchHandle = class {
constructor(handleStop, events, onEvent, onExit, checkHealth) {
this.handleStop = handleStop;
this.events = events;
this.onEvent = onEvent;
this.onExit = onExit;
this.checkHealth = checkHealth;
_defineProperty(this, "stopped", false);
this.handleEvents();
}
/**
* Stop watching the directory.
*
* `onExit` is then called asynchronously, with no error.
*/
async stop() {
var _this = this;
_this.stopped = true;
_this.handleStop();
}
iterateEvents() {
var _this2 = this;
return _wrapAsyncGenerator(function* () {
try {
var _iteratorAbruptCompletion = false;
var _didIteratorError = false;
var _iteratorError;
try {
for (var _iterator = _asyncIterator(_this2.events), _step; _iteratorAbruptCompletion = !(_step = yield _awaitAsyncGenerator(_iterator.next())).done; _iteratorAbruptCompletion = false) {
const event = _step.value;
switch (event.event.case) {
case "filesystem": yield event.event;
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (_iteratorAbruptCompletion && _iterator.return != null) yield _awaitAsyncGenerator(_iterator.return());
} finally {
if (_didIteratorError) throw _iteratorError;
}
}
} catch (err) {
if (_this2.stopped) return;
throw yield _awaitAsyncGenerator(handleRpcErrorWithHealthCheck(err, _this2.checkHealth));
}
})();
}
async handleEvents() {
var _this3 = this;
let iterationError;
try {
var _iteratorAbruptCompletion2 = false;
var _didIteratorError2 = false;
var _iteratorError2;
try {
for (var _iterator2 = _asyncIterator(_this3.iterateEvents()), _step2; _iteratorAbruptCompletion2 = !(_step2 = await _iterator2.next()).done; _iteratorAbruptCompletion2 = false) {
const event = _step2.value;
{
var _this$onEvent;
const eventType = mapEventType(event.value.type);
if (eventType === void 0) continue;
await ((_this$onEvent = _this3.onEvent) === null || _this$onEvent === void 0 ? void 0 : _this$onEvent.call(_this3, {
name: event.value.name,
type: eventType,
entry: event.value.entry ? mapEntryInfo(event.value.entry) : void 0
}));
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (_iteratorAbruptCompletion2 && _iterator2.return != null) await _iterator2.return();
} finally {
if (_didIteratorError2) throw _iteratorError2;
}
}
} catch (err) {
iterationError = err;
}
try {
if (iterationError) {
var _this$onExit;
await ((_this$onExit = _this3.onExit) === null || _this$onExit === void 0 ? void 0 : _this$onExit.call(_this3, iterationError));
} else {
var _this$onExit2;
await ((_this$onExit2 = _this3.onExit) === null || _this$onExit2 === void 0 ? void 0 : _this$onExit2.call(_this3));
}
} catch (_unused) {} finally {
_this3.handleStop();
}
}
};
//#endregion
//#region src/sandbox/filesystem/index.ts
const FILESYSTEM_HTTP_ERROR_MAP = { 404: (message) => new FileNotFoundError(message) };
const FILESYSTEM_RPC_ERROR_MAP = { [_connectrpc_connect.Code.NotFound]: (message) => new FileNotFoundError(message) };
async function handleFilesystemRpcError(err, checkHealth) {
return handleRpcErrorWithHealthCheck(err, checkHealth, FILESYSTEM_RPC_ERROR_MAP);
}
function handleFilesystemEnvdApiError(res) {
return handleEnvdApiError(res, FILESYSTEM_HTTP_ERROR_MAP);
}
/**
* Sandbox filesystem object type.
*/
let FileType = /* @__PURE__ */ function(FileType) {
/**
* Filesystem object is a file.
*/
FileType["FILE"] = "file";
/**
* Filesystem object is a directory.
*/
FileType["DIR"] = "dir";
/**
* Filesystem object is a symlink.
*/
FileType["SYMLINK"] = "symlink";
return FileType;
}({});
function mapFileType(fileType) {
switch (fileType) {
case 2: return "dir";
case 1: return "file";
case 3: return "symlink";
}
}
function mapModifiedTime(modifiedTime) {
if (!modifiedTime) return void 0;
return new Date(Number(modifiedTime.seconds) * 1e3 + Math.floor(modifiedTime.nanos / 1e6));
}
function mapMetadata(metadata) {
if (!metadata) return void 0;
return Object.keys(metadata).length === 0 ? void 0 : metadata;
}
const METADATA_HEADER_PREFIX = "X-Metadata-";
const METADATA_KEY_REGEX = /^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$/;
const METADATA_VALUE_REGEX = /^[\x20-\x7e]*$/;
function validateMetadata(metadata) {
if (!metadata) return;
for (const [key, value] of Object.entries(metadata)) {
if (!METADATA_KEY_REGEX.test(key)) throw new InvalidArgumentError(`Invalid metadata key ${JSON.stringify(key)}: keys must be non-empty and use only HTTP token characters (letters, digits and !#$%&'*+-.^_\`|~).`);
if (!METADATA_VALUE_REGEX.test(value)) throw new InvalidArgumentError(`Invalid metadata value for key ${JSON.stringify(key)}: values must be printable US-ASCII.`);
}
}
function metadataHeaders(metadata) {
if (!metadata) return {};
const headers = {};
for (const [key, value] of Object.entries(metadata)) headers[`${METADATA_HEADER_PREFIX}${key}`] = value;
return headers;
}
/**
* Map a protobuf `EntryInfo` to the SDK `EntryInfo`.
*/
function mapEntryInfo(entry) {
return {
name: entry.name,
type: entry.isSymlink ? "symlink" : mapFileType(entry.type),
path: entry.path,
size: Number(entry.size),
mode: entry.mode,
permissions: entry.permissions,
owner: entry.owner,
group: entry.group,
modifiedTime: mapModifiedTime(entry.modifiedTime),
symlinkTarget: entry.symlinkTarget,
metadata: mapMetadata(entry.metadata)
};
}
/**
* Module for interacting with the sandbox filesystem.
*/
var Filesystem = class {
constructor(transport, envdApi, connectionConfig) {
this.envdApi = envdApi;
this.connectionConfig = connectionConfig;
_defineProperty(this, "rpc", void 0);
_defineProperty(this, "defaultWatchTimeout", 6e4);
_defineProperty(this, "defaultWatchRecursive", false);
_defineProperty(this, "checkHealth", void 0);
_defineProperty(this, "uploadFetch", void 0);
const uploadFetch = createEnvdUploadFetch({ proxy: connectionConfig.proxy });
this.uploadFetch = (request) => uploadFetch(request);
this.rpc = (0, _connectrpc_connect.createClient)(Filesystem$1, transport);
this.checkHealth = () => checkSandboxHealth(this.envdApi);
}
async read(path, opts) {
var _this = this;
var _opts$format;
const format = (_opts$format = opts === null || opts === void 0 ? void 0 : opts.format) !== null && _opts$format !== void 0 ? _opts$format : "text";
let user = opts === null || opts === void 0 ? void 0 : opts.user;
if (user == void 0 && (0, compare_versions.compareVersions)(_this.envdApi.version, "0.4.0") < 0) user = defaultUsername;
const headers = {};
if (opts === null || opts === void 0 ? void 0 : opts.gzip) headers["Accept-Encoding"] = "gzip";
if (format === "stream") {
var _opts$requestTimeoutM;
const requestTimeoutMs = (_opts$requestTimeoutM = opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs) !== null && _opts$requestTimeoutM !== void 0 ? _opts$requestTimeoutM : _this.connectionConfig.requestTimeoutMs;
const { controller, clearStartTimeout, cleanup } = setupRequestController(requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal);
try {
var _opts$streamIdleTimeo;
const res = await _this.envdApi.api.GET("/files", {
params: { query: {
path,
username: user
} },
parseAs: "stream",
signal: controller.signal,
headers
}).catch(async (err) => {
throw await handleEnvdApiFetchError(err, _this.checkHealth);
});
const err = await handleFilesystemEnvdApiError(res);
if (err) {
if (res.response.body && !res.response.bodyUsed) await res.response.body.cancel().catch(() => {});
cleanup();
throw err;
}
return wrapStreamWithConnectionCleanup(res.data, {
clearStartTimeout,
cleanup,
controller,
idleTimeoutMs: (_opts$streamIdleTimeo = opts === null || opts === void 0 ? void 0 : opts.streamIdleTimeoutMs) !== null && _opts$streamIdleTimeo !== void 0 ? _opts$streamIdleTimeo : requestTimeoutMs
});
} catch (err) {
cleanup();
throw err;
}
}
const res = await _this.envdApi.api.GET("/files", {
params: { query: {
path,
username: user
} },
parseAs: format === "bytes" ? "arrayBuffer" : format,
signal: _this.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal),
headers
}).catch(async (err) => {
throw await handleEnvdApiFetchError(err, _this.checkHealth);
});
const err = await handleFilesystemEnvdApiError(res);
if (err) throw err;
if (res.response.headers.get("content-length") === "0") {
if (format === "bytes") return /* @__PURE__ */ new Uint8Array(0);
return format === "blob" ? new Blob([]) : "";
}
if (format === "bytes") return new Uint8Array(res.data);
return res.data;
}
async write(pathOrFiles, dataOrOpts, opts) {
var _this2 = this;
var _writeOpts$useOctetSt;
if (typeof pathOrFiles !== "string" && !Array.isArray(pathOrFiles)) throw new Error("Path or files are required");
if (typeof pathOrFiles === "string" && Array.isArray(dataOrOpts)) throw new Error("Cannot specify both path and array of files. You have to specify either path and data for a single file or an array for multiple files.");
const { path, writeOpts, writeFiles } = typeof pathOrFiles === "string" ? {
path: pathOrFiles,
writeOpts: opts,
writeFiles: [{ data: dataOrOpts }]
} : {
path: void 0,
writeOpts: dataOrOpts,
writeFiles: pathOrFiles
};
if (writeFiles.length === 0) return [];
let user = writeOpts === null || writeOpts === void 0 ? void 0 : writeOpts.user;
if (user == void 0 && (0, compare_versions.compareVersions)(_this2.envdApi.version, "0.4.0") < 0) user = defaultUsername;
const useGzip = (writeOpts === null || writeOpts === void 0 ? void 0 : writeOpts.gzip) === true;
if (useGzip && (writeOpts === null || writeOpts === void 0 ? void 0 : writeOpts.useOctetStream) === false) throw new InvalidArgumentError("`gzip` requires the octet-stream upload and can't be combined with `useOctetStream: false`.");
const supportsOctetStream = (0, compare_versions.compareVersions)(_this2.envdApi.version, ENVD_OCTET_STREAM_UPLOAD) >= 0;
const hasStreamableData = writeFiles.some((file) => isStreamableData(file.data));
const useOctetStream = (((_writeOpts$useOctetSt = writeOpts === null || writeOpts === void 0 ? void 0 : writeOpts.useOctetStream) !== null && _writeOpts$useOctetSt !== void 0 ? _writeOpts$useOctetSt : hasStreamableData) || useGzip) && supportsOctetStream;
if (hasStreamableData && !useOctetStream) {
if (!supportsOctetStream) throw new TemplateError(`Uploading stream data requires envd ${ENVD_OCTET_STREAM_UPLOAD} or later.`);
throw new InvalidArgumentError("Stream data (`ReadableStream` / `AsyncIterable`) is only uploaded with octet-stream; `useOctetStream: false` is not supported for it.");
}
const metadata = writeOpts === null || writeOpts === void 0 ? void 0 : writeOpts.metadata;
validateMetadata(metadata);
if (metadata && Object.keys(metadata).length > 0 && (0, compare_versions.compareVersions)(_this2.envdApi.version, "0.6.2") < 0) throw new TemplateError("File metadata requires envd 0.6.2 or later.");
const extraHeaders = metadataHeaders(metadata);
const results = [];
if (useOctetStream) {
const headers = _objectSpread2({ "Content-Type": "application/octet-stream" }, extraHeaders);
if (useGzip) headers["Content-Encoding"] = "gzip";
const uploadResults = await Promise.all(writeFiles.map(async (file) => {
const filePath = path !== null && path !== void 0 ? path : file.path;
const { body, streamed, sourceError } = await toUploadBody(file.data, useGzip);
const signal = streamed ? writeOpts === null || writeOpts === void 0 ? void 0 : writeOpts.signal : _this2.connectionConfig.getSignal(writeOpts === null || writeOpts === void 0 ? void 0 : writeOpts.requestTimeoutMs, writeOpts === null || writeOpts === void 0 ? void 0 : writeOpts.signal);
const res = await _this2.envdApi.api.POST("/files", _objectSpread2({
params: { query: {
path: filePath,
username: user
} },
bodySerializer: () => body,
headers,
signal,
body: {},
fetch: _this2.uploadFetch
}, streamed && { duplex: "half" })).catch(async (err) => {
const invalidChunk = sourceError();
if (invalidChunk) throw invalidChunk;
throw await handleEnvdApiFetchError(err, _this2.checkHealth);
});
const err = await handleFilesystemEnvdApiError(res);
if (err) throw err;
const files = res.data;
if (!files || files.length === 0) throw new Error("Expected to receive information about written file");
for (const f of files) f.metadata = mapMetadata(f.metadata);
return files;
}));
for (const files of uploadResults) results.push(...files);
} else {
const formData = new FormData();
for (const file of writeFiles) {
var _path;
formData.append("file", await toBlob(file.data), (_path = file.path) !== null && _path !== void 0 ? _path : path);
}
const res = await _this2.envdApi.api.POST("/files", {
params: { query: {
path,
username: user
} },
bodySerializer: () => formData,
headers: extraHeaders,
signal: _this2.connectionConfig.getSignal(writeOpts === null || writeOpts === void 0 ? void 0 : writeOpts.requestTimeoutMs, writeOpts === null || writeOpts === void 0 ? void 0 : writeOpts.signal),
body: {},
fetch: _this2.uploadFetch
}).catch(async (err) => {
throw await handleEnvdApiFetchError(err, _this2.checkHealth);
});
const err = await handleFilesystemEnvdApiError(res);
if (err) throw err;
const files = res.data;
if (!files || files.length === 0) throw new Error("Expected to receive information about written file");
for (const f of files) f.metadata = mapMetadata(f.metadata);
results.push(...files);
}
return results.length === 1 && path ? results[0] : results;
}
/**
* Write multiple files.
*
*
* Writing to a file that doesn't exist creates the file.
*
* Writing to a file that already exists overwrites the file.
*
* Writing to a file at path that doesn't exist creates the necessary directories.
*
* @param files list of files to write as `WriteEntry` objects, each containing `path` and `data`.
* @param opts connection options.
*
* @returns information about the written files
*/
async writeFiles(files, opts) {
return this.write(files, opts);
}
/**
* List entries in a directory.
*
* @param path path to the directory.
* @param opts connection options.
*
* @returns list of entries in the sandbox filesystem directory.
*/
async list(path, opts) {
var _this4 = this;
if (typeof (opts === null || opts === void 0 ? void 0 : opts.depth) === "number" && opts.depth < 1) throw new InvalidArgumentError("depth should be at least one");
try {
var _opts$depth;
const res = await _this4.rpc.listDir({
path,
depth: (_opts$depth = opts === null || opts === void 0 ? void 0 : opts.depth) !== null && _opts$depth !== void 0 ? _opts$depth : 1
}, {
headers: authenticationHeader(_this4.envdApi.version, opts === null || opts === void 0 ? void 0 : opts.user),
signal: _this4.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal)
});
const entries = [];
for (const e of res.entries) {
if (!mapFileType(e.type)) continue;
entries.push(mapEntryInfo(e));
}
return entries;
} catch (err) {
throw await handleFilesystemRpcError(err, _this4.checkHealth);
}
}
/**
* Create a new directory and all directories along the way if needed on the specified path.
*
* @param path path to a new directory. For example '/dirA/dirB' when creating 'dirB'.
* @param opts connection options.
*
* @returns `true` if the directory was created, `false` if it already exists.
*/
async makeDir(path, opts) {
var _this5 = this;
try {
await _this5.rpc.makeDir({ path }, {
headers: authenticationHeader(_this5.envdApi.version, opts === null || opts === void 0 ? void 0 : opts.user),
signal: _this5.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal)
});
return true;
} catch (err) {
if (err instanceof _connectrpc_connect.ConnectError) {
if (err.code === _connectrpc_connect.Code.AlreadyExists) return false;
}
throw await handleFilesystemRpcError(err, _this5.checkHealth);
}
}
/**
* Rename a file or directory.
*
* @param oldPath path to the file or directory to rename.
* @param newPath new path for the file or directory.
* @param opts connection options.
*
* @returns information about renamed file or directory.
*/
async rename(oldPath, newPath, opts) {
var _this6 = this;
try {
const entry = (await _this6.rpc.move({
source: oldPath,
destination: newPath
}, {
headers: authenticationHeader(_this6.envdApi.version, opts === null || opts === void 0 ? void 0 : opts.user),
signal: _this6.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal)
})).entry;
if (!entry) throw new Error("Expected to receive information about moved object");
return mapEntryInfo(entry);
} catch (err) {
throw await handleFilesystemRpcError(err, _this6.checkHealth);
}
}
/**
* Remove a file or directory.
*
* @param path path to a file or directory.
* @param opts connection options.
*/
async remove(path, opts) {
var _this7 = this;
try {
await _this7.rpc.remove({ path }, {
headers: authenticationHeader(_this7.envdApi.version, opts === null || opts === void 0 ? void 0 : opts.user),
signal: _this7.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal)
});
} catch (err) {
throw await handleFilesystemRpcError(err, _this7.checkHealth);
}
}
/**
* Check if a file or a directory exists.
*
* @param path path to a file or a directory
* @param opts connection options.
*
* @returns `true` if the file or directory exists, `false` otherwise
*/
async exists(path, opts) {
var _this8 = this;
try {
await _this8.rpc.stat({ path }, {
headers: authenticationHeader(_this8.envdApi.version, opts === null || opts === void 0 ? void 0 : opts.user),
signal: _this8.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal)
});
return true;
} catch (err) {
if (err instanceof _connectrpc_connect.ConnectError) {
if (err.code === _connectrpc_connect.Code.NotFound) return false;
}
throw await handleFilesystemRpcError(err, _this8.checkHealth);
}
}
/**
* Get information about a file or directory.
*
* @param path path to a file or directory.
* @param opts connection options.
*
* @returns information about the file or directory like name, type, and path.
*/
async getInfo(path, opts) {
var _this9 = this;
try {
const res = await _this9.rpc.stat({ path }, {
headers: authenticationHeader(_this9.envdApi.version, opts === null || opts === void 0 ? void 0 : opts.user),
signal: _this9.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal)
});
if (!res.entry) throw new Error("Expected to receive information about the file or directory");
return mapEntryInfo(res.entry);
} catch (err) {
throw await handleFilesystemRpcError(err, _this9.checkHealth);
}
}
/**
* Start watching a directory for filesystem events.
*
* @param path path to directory to watch.
* @param onEvent callback to call when an event in the directory occurs.
* @param opts connection options.
*
* @returns `WatchHandle` object for stopping watching directory.
*/
async watchDir(path, onEvent, opts) {
var _this10 = this;
var _opts$requestTimeoutM2, _opts$recursive, _opts$includeEntry, _opts$allowNetworkMou, _opts$timeoutMs;
if ((opts === null || opts === void 0 ? void 0 : opts.recursive) && _this10.envdApi.version && (0, compare_versions.compareVersions)(_this10.envdApi.version, "0.1.4") < 0) throw new TemplateError("You need to update the template to use recursive watching.");
if ((opts === null || opts === void 0 ? void 0 : opts.includeEntry) && _this10.envdApi.version && (0, compare_versions.compareVersions)(_this10.envdApi.version, "0.6.3") < 0) throw new TemplateError("You need to update the template to include entry info in watch events.");
if ((opts === null || opts === void 0 ? void 0 : opts.allowNetworkMounts) && _this10.envdApi.version && (0, compare_versions.compareVersions)(_this10.envdApi.version, "0.6.4") < 0) throw new TemplateError("You need to update the template to watch directories on network mounts.");
const { controller, clearStartTimeout, cleanup } = setupRequestController((_opts$requestTimeoutM2 = opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs) !== null && _opts$requestTimeoutM2 !== void 0 ? _opts$requestTimeoutM2 : _this10.connectionConfig.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal);
const events = _this10.rpc.watchDir({
path,
recursive: (_opts$recursive = opts === null || opts === void 0 ? void 0 : opts.recursive) !== null && _opts$recursive !== void 0 ? _opts$recursive : _this10.defaultWatchRecursive,
includeEntry: (_opts$includeEntry = opts === null || opts === void 0 ? void 0 : opts.includeEntry) !== null && _opts$includeEntry !== void 0 ? _opts$includeEntry : false,
allowNetworkMounts: (_opts$allowNetworkMou = opts === null || opts === void 0 ? void 0 : opts.allowNetworkMounts) !== null && _opts$allowNetworkMou !== void 0 ? _opts$allowNetworkMou : false
}, {
headers: _objectSpread2(_objectSpread2({}, authenticationHeader(_this10.envdApi.version, opts === null || opts === void 0 ? void 0 : opts.user)), {}, { [KEEPALIVE_PING_HEADER]: 50 .toString() }),
signal: controller.signal,
timeoutMs: (_opts$timeoutMs = opts === null || opts === void 0 ? void 0 : opts.timeoutMs) !== null && _opts$timeoutMs !== void 0 ? _opts$timeoutMs : _this10.defaultWatchTimeout
});
try {
await handleWatchDirStartEvent(events);
clearStartTimeout();
return new WatchHandle(cleanup, events, onEvent, opts === null || opts === void 0 ? void 0 : opts.onExit, _this10.checkHealth);
} catch (err) {
cleanup();
throw await handleFilesystemRpcError(err, _this10.checkHealth);
}
}
};
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/asyncGeneratorDelegate.js
function _asyncGeneratorDelegate(t) {
var e = {}, n = !1;
function pump(e, r) {
return n = !0, r = new Promise(function(n) {
n(t[e](r));
}), {
done: !1,
value: new _OverloadYield(r, 1)
};
}
return e["undefined" != typeof Symbol && Symbol.iterator || "@@iterator"] = function() {
return this;
}, e.next = function(t) {
return n ? (n = !1, t) : pump("next", t);
}, "function" == typeof t["throw"] && (e["throw"] = function(t) {
if (n) throw n = !1, t;
return pump("throw", t);
}), "function" == typeof t["return"] && (e["return"] = function(t) {
return n ? (n = !1, t) : pump("return", t);
}), e;
}
//#endregion
//#region src/sandbox/commands/commandHandle.ts
/**
* Error thrown when a command exits with a non-zero exit code.
*/
var CommandExitError = class extends SandboxError {
constructor(result) {
super(result.error);
this.result = result;
this.name = "CommandExitError";
}
/**
* Command execution exit code.
* `0` if the command finished successfully.
*/
get exitCode() {
return this.result.exitCode;
}
/**
* Error message from command execution.
*/
get error() {
return this.result.error;
}
/**
* Command execution stdout output.
* `undefined` if the command was started with `retainOutput: false`.
*/
get stdout() {
return this.result.stdout;
}
/**
* Command execution stderr output.
* `undefined` if the command was started with `retainOutput: false`.
*/
get stderr() {
return this.result.stderr;
}
};
/**
* Command execution handle.
*
* It provides methods for waiting for the command to finish, retrieving stdout/stderr, and killing the command.
*
* @property {number} pid process ID of the command.
*/
var CommandHandle = class {
/**
* @hidden
* @internal
* @access protected
*/
constructor(pid, handleDisconnect, handleKill, events, onStdout, onStderr, onPty, handleSendStdin, handleCloseStdin, checkHealth, retainOutput = true) {
this.pid = pid;
this.handleDisconnect = handleDisconnect;
this.handleKill = handleKill;
this.events = events;
this.onStdout = onStdout;
this.onStderr = onStderr;
this.onPty = onPty;
this.handleSendStdin = handleSendStdin;
this.handleCloseStdin = handleCloseStdin;
this.checkHealth = checkHealth;
_defineProperty(this, "_stdout", void 0);
_defineProperty(this, "_stderr", void 0);
_defineProperty(this, "stdoutDecoder", new TextDecoder());
_defineProperty(this, "stderrDecoder", new TextDecoder());
_defineProperty(this, "result", void 0);
_defineProperty(this, "iterationError", void 0);
_defineProperty(this, "disconnected", false);
_defineProperty(this, "_wait", void 0);
if (retainOutput) {
this._stdout = "";
this._stderr = "";
}
this._wait = this.handleEvents();
}
/**
* Command execution exit code.
* `0` if the command finished successfully.
*
* It is `undefined` if the command is still running.
*/
get exitCode() {
var _this$result;
return (_this$result = this.result) === null || _this$result === void 0 ? void 0 : _this$result.exitCode;
}
/**
* Error message from command execution.
*/
get error() {
var _this$result2;
return (_this$result2 = this.result) === null || _this$result2 === void 0 ? void 0 : _this$result2.error;
}
/**
* Command execution stderr output.
* `undefined` if the command was started with `retainOutput: false`.
*/
get stderr() {
return this._stderr;
}
/**
* Command execution stdout output.
* `undefined` if the command was started with `retainOutput: false`.
*/
get stdout() {
return this._stdout;
}
/**
* Wait for the command to finish and return the result.
* If the command exits with a non-zero exit code, it throws a `CommandExitError`.
*
* @returns `CommandResult` result of command execution.
*/
async wait() {
var _this = this;
await _this._wait;
if (_this.iterationError) throw _this.iterationError;
if (!_this.result) throw new SandboxError("Process exited without a result");
if (_this.result.exitCode !== 0) throw new CommandExitError(_this.result);
return _this.result;
}
/**
* Disconnect from the command.
*
* The command is not killed, but SDK stops receiving events from the command.
* You can reconnect to the command using {@link Commands.connect}.
*
* Once it returns, the `onStdout`/`onStderr`/`onPty` callbacks are guaranteed
* not to fire for output produced after this call. It does not wait for the
* event handler to drain, so it returns promptly even for an idle command
* whose stream produces no further output.
*/
async disconnect() {
var _this2 = this;
_this2.disconnected = true;
_this2.handleDisconnect();
}
/**
* Kill the command.
* It uses `SIGKILL` signal to kill the command.
*
* @returns `true` if the command was killed successfully, `false` if the command was not found.
*/
async kill() {
return await this.handleKill();
}
/**
* Send data to the command stdin.
*
* The command must have been started with `stdin: true`.
*
* @param data data to send to the command.
* @param opts connection options.
*/
async sendStdin(data, opts) {
var _this4 = this;
if (!_this4.handleSendStdin) throw new SandboxError("Sending stdin is not supported for this command handle.");
await _this4.handleSendStdin(data, opts);
}
/**
* Close the command stdin.
*
* This signals EOF to the command. The command must have been started with
* `stdin: true`.
*
* @param opts connection options.
*/
async closeStdin(opts) {
var _this5 = this;
if (!_this5.handleCloseStdin) throw new SandboxError("Closing stdin is not supported for this command handle.");
await _this5.handleCloseStdin(opts);
}
appendStdout(out) {
if (this._stdout !== void 0) this._stdout += out;
}
appendStderr(out) {
if (this._stderr !== void 0) this._stderr += out;
}
/**
* Flush any bytes still buffered in the stream decoders.
*
* Incomplete trailing UTF-8 sequences are emitted as replacement
* characters, matching the per-chunk decoding behavior.
*/
*flushDecoders() {
const stdoutRest = this.stdoutDecoder.decode();
if (stdoutRest) {
this.appendStdout(stdoutRest);
yield [
stdoutRest,
null,
null
];
}
const stderrRest = this.stderrDecoder.decode();
if (stderrRest) {
this.appendStderr(stderrRest);
yield [
null,
stderrRest,
null
];
}
}
iterateEvents() {
var _this6 = this;
return _wrapAsyncGenerator(function* () {
try {
var _iteratorAbruptCompletion = false;
var _didIteratorError = false;
var _iteratorError;
try {
for (var _iterator = _asyncIterator(_this6.events), _step; _iteratorAbruptCompletion = !(_step = yield _awaitAsyncGenerator(_iterator.next())).done; _iteratorAbruptCompletion = false) {
const event = _step.value;
{
var _event$event;
const e = event === null || event === void 0 || (_event$event = event.event) === null || _event$event === void 0 ? void 0 : _event$event.event;
let out;
switch (e === null || e === void 0 ? void 0 : e.case) {
case "data":
switch (e.value.output.case) {
case "stdout":
out = _this6.stdoutDecoder.decode(e.value.output.value, { stream: true });
if (out) {
_this6.appendStdout(out);
yield [
out,
null,
null
];
}
break;
case "stderr":
out = _this6.stderrDecoder.decode(e.value.output.value, { stream: true });
if (out) {
_this6.appendStderr(out);
yield [
null,
out,
null
];
}
break;
case "pty": yield [
null,
null,
e.value.output.value
];
}
break;
case "end": {
const flushed = [..._this6.flushDecoders()];
_this6.result = {
exitCode: e.value.exitCode,
error: e.value.error,
stdout: _this6.stdout,
stderr: _this6.stderr
};
for (const chunk of flushed) yield chunk;
break;
}
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (_iteratorAbruptCompletion && _iterator.return != null) yield _awaitAsyncGenerator(_iterator.return());
} finally {
if (_didIteratorError) throw _iteratorError;
}
}
} catch (e) {
yield* _asyncGeneratorDelegate(_asyncIterator(_this6.flushDecoders()));
throw e;
}
if (_this6.result === void 0) yield* _asyncGeneratorDelegate(_asyncIterator(_this6.flushDecoders()));
})();
}
async handleEvents() {
var _this7 = this;
try {
var _iteratorAbruptCompletion2 = false;
var _didIteratorError2 = false;
var _iteratorError2;
try {
for (var _iterator2 = _asyncIterator(_this7.iterateEvents()), _step2; _iteratorAbruptCompletion2 = !(_step2 = await _iterator2.next()).done; _iteratorAbruptCompletion2 = false) {
const [stdout, stderr, pty] = _step2.value;
if (_this7.disconnected) break;
if (stdout !== null) {
var _this$onStdout;
await ((_this$onStdout = _this7.onStdout) === null || _this$onStdout === void 0 ? void 0 : _this$onStdout.call(_this7, stdout));
} else if (stderr !== null) {
var _this$onStderr;
await ((_this$onStderr = _this7.onStderr) === null || _this$onStderr === void 0 ? void 0 : _this$onStderr.call(_this7, stderr));
} else if (pty) {
var _this$onPty;
await ((_this$onPty = _this7.onPty) === null || _this$onPty === void 0 ? void 0 : _this$onPty.call(_this7, pty));
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (_iteratorAbruptCompletion2 && _iterator2.return != null) await _iterator2.return();
} finally {
if (_didIteratorError2) throw _iteratorError2;
}
}
} catch (e) {
_this7.iterationError = await handleRpcErrorWithHealthCheck(e, _this7.checkHealth);
} finally {
_this7.handleDisconnect();
}
}
};
//#endregion
//#region src/sandbox/commands/inputHandle.ts
const CLOSE = Symbol("close");
/**
* An ordered, long-lived input handle for a command or PTY.
*/
var CommandInputHandle = class {
/**
* @hidden
* @internal
*/
constructor(rpc, pid, inputKind, opts, checkHealth, keepaliveIntervalMs = 5e4) {
var _this = this;
var _opts$timeoutMs;
this.rpc = rpc;
this.pid = pid;
this.inputKind = inputKind;
this.checkHealth = checkHealth;
_defineProperty(this, "queue", []);
_defineProperty(this, "completion", void 0);
_defineProperty(this, "keepaliveIntervalMs", void 0);
_defineProperty(this, "wake", void 0);
_defineProperty(this, "closing", false);
_defineProperty(this, "stopped", false);
_defineProperty(this, "failure", void 0);
_defineProperty(this, "closePromise", void 0);
this.keepaliveIntervalMs = keepaliveIntervalMs;
this.completion = this.rpc.streamInput(this.requests(), {
signal: opts.signal,
timeoutMs: (_opts$timeoutMs = opts.timeoutMs) !== null && _opts$timeoutMs !== void 0 ? _opts$timeoutMs : STREAM_INPUT_TIMEOUT_MS
}).then(() => {
this.stopped = true;
this.notify();
}).catch(async (error) => {
_this.failure = await handleRpcErrorWithHealthCheck(error, _this.checkHealth);
_this.stopped = true;
_this.notify();
throw _this.failure;
});
this.completion.catch(() => void 0);
}
/**
* Enqueue input for transmission in call order.
*
* @param data Input data to send.
* @returns A promise that resolves when the input is queued for ordered transmission.
* @throws {NotFoundError} If the command or PTY does not exist.
* @throws {TimeoutError} If the input stream times out.
* @throws {SandboxError} If this handle is closed.
*/
async send(data) {
var _this2 = this;
if (_this2.failure) throw _this2.failure;
if (_this2.closing || _this2.stopped) throw new SandboxError("Cannot send input after the input handle is closed. Open a new one with `sandbox.commands.streamInput(pid)` or `sandbox.pty.streamInput(pid)`.");
_this2.queue.push(data instanceof Uint8Array ? data.slice() : data);
_this2.notify();
}
/**
* Finish the input stream and wait for envd to acknowledge it.
*
* @returns A promise that resolves when envd acknowledges the end of the input stream.
* @throws {NotFoundError} If the command or PTY does not exist.
* @throws {TimeoutError} If the input stream times out.
*/
close() {
if (this.closePromise) return this.closePromise;
this.closing = true;
this.queue.push(CLOSE);
this.notify();
this.closePromise = this.completion;
return this.closePromise;
}
notify() {
var _this$wake;
(_this$wake = this.wake) === null || _this$wake === void 0 || _this$wake.call(this);
}
waitForInput() {
return new Promise((resolve) => {
let settled = false;
const timer = setTimeout(() => {
settled = true;
this.wake = void 0;
resolve(false);
}, this.keepaliveIntervalMs);
this.wake = () => {
if (settled) return;
settled = true;
clearTimeout(timer);
this.wake = void 0;
resolve(true);
};
});
}
requests() {
var _this3 = this;
return _wrapAsyncGenerator(function* () {
yield (0, _bufbuild_protobuf.create)(StreamInputRequestSchema, { event: {
case: "start",
value: { process: { selector: {
case: "pid",
value: _this3.pid
} } }
} });
while (!_this3.stopped) {
if (_this3.queue.length > 0) {
const input = _this3.queue.shift();
if (input === CLOSE) return;
const value = typeof input === "string" ? new TextEncoder().encode(input) : input;
yield (0, _bufbuild_protobuf.create)(StreamInputRequestSchema, { event: {
case: "data",
value: { input: { input: {
case: _this3.inputKind,
value
} } }
} });
continue;
}
if (!(yield _awaitAsyncGenerator(_this3.waitForInput())) && !_this3.stopped && _this3.queue.length === 0) yield (0, _bufbuild_protobuf.create)(StreamInputRequestSchema, { event: {
case: "keepalive",
value: {}
} });
}
})();
}
};
//#endregion
//#region src/paginator.ts
/**
* Generic, reusable paginator for cursor-based list endpoints.
*
* The base owns the shared pagination state — `hasNext`, `nextToken`, and the
* reading of the `x-next-token` response header (via {@link Paginator.updatePagination}).
* Each concrete paginator implements {@link Paginator.nextItems} to do the
* actual fetching for its endpoint, so any model can expose pagination by
* subclassing this without reimplementing the bookkeeping.
*
* The optional `O` type parameter is the per-call options type accepted by
* `nextItems` (e.g. connection options for a given API).
*
* @example
* ```ts
* const paginator = Sandbox.list()
* while (paginator.hasNext) {
* const items = await paginator.nextItems()
* console.log(items)
* }
* ```
*/
var Paginator = class {
constructor(opts, limit, nextToken) {
_defineProperty(this, "opts", void 0);
_defineProperty(this, "limit", void 0);
_defineProperty(this, "_hasNext", void 0);
_defineProperty(this, "_nextToken", void 0);
this.opts = opts;
this.limit = limit;
this._hasNext = true;
this._nextToken = nextToken;
}
/**
* Returns true if there are more items to fetch.
*/
get hasNext() {
return this._hasNext;
}
/**
* Returns the next token to use for pagination.
*/
get nextToken() {
return this._nextToken;
}
/**
* Update the pagination state from a response, reading the `x-next-token`
* header. Concrete paginators call this from {@link Paginator.nextItems}
* after fetching a page.
*/
updatePagination(response) {
this._nextToken = response.headers.get("x-next-token") || void 0;
this._hasNext = !!this._nextToken;
}
};
//#endregion
//#region src/secret.ts
const INVALID_SECRET_NAME_CHARS = new RegExp("[{}\\p{Cc}]", "u");
function validateSecretName(name) {
if (name.length === 0 || INVALID_SECRET_NAME_CHARS.test(name)) throw new InvalidArgumentError(`secret name ${JSON.stringify(name)} is not usable: a secret name cannot be empty or contain '{', '}' or control characters, because it is interpolated into the '\${e2b.secrets.<name>}' placeholder the runtime resolves.`);
}
function convertSecretInfo(secret) {
var _secret$metadata;
return {
secretId: secret.secretID,
name: secret.name,
version: secret.currentVersion,
metadata: (_secret$metadata = secret.metadata) !== null && _secret$metadata !== void 0 ? _secret$metadata : {},
createdAt: new Date(secret.createdAt),
updatedAt: new Date(secret.updatedAt)
};
}
/**
* Paginator for listing secrets.
*
* @example
* ```ts
* const paginator = Secret.list()
* while (paginator.hasNext) {
* const secrets = await paginator.nextItems()
* console.log(secrets)
* }
* ```
*/
var SecretPaginator = class extends Paginator {
constructor(opts) {
super(opts, opts === null || opts === void 0 ? void 0 : opts.limit, opts === null || opts === void 0 ? void 0 : opts.nextToken);
}
async nextItems(opts) {
var _this = this;
var _res$data;
if (!_this.hasNext) throw new Error("No more items to fetch");
const apiOpts = ConnectionConfig.mergeOpts(_this.opts, opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.GET("/secrets", {
params: { query: {
limit: _this.limit,
nextToken: _this.nextToken
} },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
const err = handleApiError(res, SecretError);
if (err) throw err;
_this.updatePagination(res.response);
return ((_res$data = res.data) !== null && _res$data !== void 0 ? _res$data : []).map(convertSecretInfo);
}
};
/**
* Module for managing E2B secrets and workload identity helpers.
*
* Secret values are write-only: they are accepted by {@link Secret.create}
* and {@link Secret.update} but never returned by any read surface.
*/
var Secret = class extends ClientFactory {
/**
* Create a new secret and its first value.
*
* @param name name of the secret, unique within the project.
* @param value secret value. Write-only — never returned by the API.
* @param opts connection options.
*
* @returns the secret's ID, name, current version (`1` for a new secret),
* metadata, and creation and update times.
*/
static async create(name, value, opts) {
const apiOpts = this.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.POST("/secrets", {
body: {
name,
value,
metadata: apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.metadata
},
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
const err = handleApiError(res, SecretError);
if (err) throw err;
if (!res.data) throw new Error("Response data is missing");
return convertSecretInfo(res.data);
}
/**
* Update a secret's value by storing it as the secret's new version.
*
* @param secret secret ID or name.
* @param value new secret value. Write-only — never returned by the API.
* @param opts connection options.
*
* @returns the secret's ID, name, new current version, metadata, and
* creation and update times.
*/
static async update(secret, value, opts) {
const apiOpts = this.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.POST("/secrets/{secretID}", {
params: { path: { secretID: secret } },
body: {
value,
metadata: apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.metadata
},
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (res.response.status === 404) throw new SecretNotFoundError(`Secret ${secret} not found`);
const err = handleApiError(res, SecretError);
if (err) throw err;
if (!res.data) throw new Error("Response data is missing");
return convertSecretInfo(res.data);
}
/**
* Get a secret's metadata.
*
* @param secret secret ID or name.
* @param opts connection options.
*
* @returns the secret's ID, name, current version, metadata, and creation
* and update times.
*/
static async getInfo(secret, opts) {
const apiOpts = this.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.GET("/secrets/{secretID}", {
params: { path: { secretID: secret } },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (res.response.status === 404) throw new SecretNotFoundError(`Secret ${secret} not found`);
const err = handleApiError(res, SecretError);
if (err) throw err;
if (!res.data) throw new Error("Response data is missing");
return convertSecretInfo(res.data);
}
/**
* List the project's secrets.
*
* @param opts connection options.
*
* @returns paginator over the project's secrets. Drain it page by page:
* `while (paginator.hasNext) { const secrets = await paginator.nextItems() }`.
*
* @example
* ```ts
* const paginator = Secret.list({ limit: 50 })
* while (paginator.hasNext) {
* const secrets = await paginator.nextItems()
* }
* ```
*/
static list(opts) {
return new SecretPaginator(this.resolveOpts(opts));
}
/**
* Check whether a secret exists.
*
* @param secret secret ID or name.
* @param opts connection options.
*
* @returns `true` if the secret exists, `false` otherwise.
*/
static async exists(secret, opts) {
var _this5 = this;
try {
await _this5.getInfo(secret, opts);
return true;
} catch (err) {
if (err instanceof SecretNotFoundError) return false;
throw err;
}
}
/**
* Destroy a secret, making all its versions inaccessible.
*
* @param secret secret ID or name.
* @param opts connection options.
*
* @returns `true` if the secret was destroyed, `false` if it was not found.
*/
static async destroy(secret, opts) {
const apiOpts = this.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.DELETE("/secrets/{secretID}", {
params: { path: { secretID: secret } },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (res.response.status === 404) return false;
const err = handleApiError(res, SecretError);
if (err) throw err;
return true;
}
/**
* Format a placeholder that the runtime resolves to the secret's current
* value.
*
* This is a local formatting helper and makes no network call — it does
* not check whether the named secret exists. An unknown reference fails
* server-side when the placeholder is resolved.
*
* @param secret secret name.
*
* @returns placeholder string resolving to the secret's value.
*
* @example
* ```ts
* Secret.fill('openai-api-key')
* // '${e2b.secrets.openai-api-key}'
* ```
*/
static fill(secret) {
validateSecretName(secret);
return `\${e2b.secrets.${secret}}`;
}
/**
* Define a workload identity token to pass to `iam.tokens` when creating
* a sandbox.
*
* @param token workload token definition.
*
* @returns a token definition passable to `iam.tokens`.
*
* @example
* ```ts
* const sandbox = await Sandbox.create({
* iam: {
* tokens: {
* aws: Secret.iamToken({
* audience: 'sts.amazonaws.com',
* tokenType: 'JWT-SVID',
* }),
* },
* },
* })
* ```
*/
static iamToken(token) {
return _objectSpread2({}, token);
}
};
//#endregion
//#region src/sandbox/network.ts
/**
* CIDR range that represents all traffic.
*/
const ALL_TRAFFIC = "0.0.0.0/0";
//#endregion
//#region src/sandbox/git/utils.ts
/**
* Add HTTP(S) credentials to a Git URL.
*
* @param url Git repository URL.
* @param username Username for HTTP(S) authentication.
* @param password Password or token for HTTP(S) authentication.
* @returns URL with embedded credentials.
*/
function withCredentials(url, username, password) {
if (!username && !password) return url;
if (!username || !password) throw new InvalidArgumentError("Both username and password are required when using Git credentials.");
let parsed;
try {
parsed = new URL(url);
} catch (_unused) {
throw new InvalidArgumentError(`Invalid Git URL: ${url}`);
}
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new InvalidArgumentError("Only http(s) Git URLs support username/password credentials.");
parsed.username = username;
parsed.password = password;
return parsed.toString();
}
/**
* Strip HTTP(S) credentials from a Git URL.
*
* @param url Git repository URL.
* @returns URL without embedded credentials.
*/
function stripCredentials(url) {
let parsed;
try {
parsed = new URL(url);
} catch (_unused2) {
return url;
}
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return url;
if (!parsed.username && !parsed.password) return url;
parsed.username = "";
parsed.password = "";
return parsed.toString();
}
/**
* Derive the default repository directory name from a Git URL.
*
* @param url Git repository URL.
* @returns Repository directory name, if it can be determined.
*/
function deriveRepoDirFromUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch (_unused3) {
return;
}
const lastSegment = parsed.pathname.replace(/\/+$/, "").split("/").pop();
if (!lastSegment) return;
return lastSegment.endsWith(".git") ? lastSegment.slice(0, -4) : lastSegment;
}
/**
* Build a shell-safe git command string.
*
* @param args Git command arguments.
* @param repoPath Repository path for `git -C`, if provided.
* @returns Shell-safe git command.
*/
function buildGitCommand(args, repoPath) {
const parts = ["git"];
if (repoPath) parts.push("-C", repoPath);
parts.push(...args);
return parts.map((part) => shellQuote(part)).join(" ");
}
function buildPushArgs(remoteName, opts) {
const { remote, branch, setUpstream } = opts;
const args = ["push"];
const targetRemote = remoteName !== null && remoteName !== void 0 ? remoteName : remote;
if (setUpstream && targetRemote) args.push("--set-upstream");
if (targetRemote) args.push(targetRemote);
if (branch) args.push(branch);
return args;
}
function parseAheadBehind(segment) {
if (!segment) return {
ahead: 0,
behind: 0
};
let ahead = 0;
let behind = 0;
if (segment.includes("ahead")) try {
ahead = Number.parseInt(segment.split("ahead")[1].split(",")[0].trim(), 10);
} catch (_unused4) {
ahead = 0;
}
if (segment.includes("behind")) try {
behind = Number.parseInt(segment.split("behind")[1].split(",")[0].trim(), 10);
} catch (_unused5) {
behind = 0;
}
return {
ahead,
behind
};
}
function normalizeBranchName(name) {
if (name.startsWith("HEAD (detached at ")) return name.replace("HEAD (detached at ", "").replace(/\)$/, "");
return name.replace("HEAD (no branch)", "HEAD").replace("No commits yet on ", "").replace("Initial commit on ", "");
}
function deriveStatus(indexStatus, workingStatus) {
const statuses = /* @__PURE__ */ new Set([indexStatus, workingStatus]);
if (statuses.has("U")) return "conflict";
if (statuses.has("R")) return "renamed";
if (statuses.has("C")) return "copied";
if (statuses.has("D")) return "deleted";
if (statuses.has("A")) return "added";
if (statuses.has("M")) return "modified";
if (statuses.has("T")) return "typechange";
if (statuses.has("?")) return "untracked";
return "unknown";
}
/**
* Parse `git status --porcelain=1 -b` output into a structured object.
*
* @param output Git status output.
* @returns Parsed {@link GitStatus}.
*/
function parseGitStatus(output) {
const lines = output.split("\n").map((line) => line.replace(/\r$/, "")).filter((line) => line.trim().length > 0);
let currentBranch;
let upstream;
let ahead = 0;
let behind = 0;
let detached = false;
const fileStatus = [];
if (lines.length === 0) return {
currentBranch,
upstream,
ahead,
behind,
detached,
fileStatus,
isClean: true,
hasChanges: false,
hasStaged: false,
hasUntracked: false,
hasConflicts: false,
totalCount: 0,
stagedCount: 0,
unstagedCount: 0,
untrackedCount: 0,
conflictCount: 0
};
const branchLine = lines[0];
if (branchLine.startsWith("## ")) {
const branchInfo = branchLine.slice(3);
const aheadStart = branchInfo.indexOf(" [");
const branchPart = aheadStart === -1 ? branchInfo : branchInfo.slice(0, aheadStart);
const aheadPart = aheadStart === -1 ? void 0 : branchInfo.slice(aheadStart + 2, -1);
const normalizedBranch = normalizeBranchName(branchPart);
const rawBranch = branchPart;
if (rawBranch.startsWith("HEAD (detached at ") || rawBranch.includes("detached") || normalizedBranch.startsWith("HEAD")) detached = true;
else if (normalizedBranch.includes("...")) {
const [branch, upstreamBranch] = normalizedBranch.split("...");
currentBranch = branch || void 0;
upstream = upstreamBranch || void 0;
} else currentBranch = normalizedBranch || void 0;
const aheadBehind = parseAheadBehind(aheadPart);
ahead = aheadBehind.ahead;
behind = aheadBehind.behind;
}
for (const line of lines.slice(1)) {
if (line.startsWith("?? ")) {
const name = line.slice(3);
fileStatus.push({
name,
status: "untracked",
indexStatus: "?",
workingTreeStatus: "?",
staged: false
});
continue;
}
if (line.length < 3) continue;
const indexStatus = line[0];
const workingTreeStatus = line[1];
const path = line.slice(3);
let renamedFrom;
let name = path;
if (path.includes(" -> ")) {
const parts = path.split(" -> ");
renamedFrom = parts[0];
name = parts.slice(1).join(" -> ");
}
fileStatus.push(_objectSpread2({
name,
status: deriveStatus(indexStatus, workingTreeStatus),
indexStatus,
workingTreeStatus,
staged: indexStatus !== " " && indexStatus !== "?"
}, renamedFrom ? { renamedFrom } : {}));
}
const totalCount = fileStatus.length;
const stagedCount = fileStatus.filter((item) => item.staged).length;
const untrackedCount = fileStatus.filter((item) => item.status === "untracked").length;
const conflictCount = fileStatus.filter((item) => item.status === "conflict").length;
const unstagedCount = totalCount - stagedCount;
return {
currentBranch,
upstream,
ahead,
behind,
detached,
fileStatus,
isClean: totalCount === 0,
hasChanges: totalCount > 0,
hasStaged: stagedCount > 0,
hasUntracked: untrackedCount > 0,
hasConflicts: conflictCount > 0,
totalCount,
stagedCount,
unstagedCount,
untrackedCount,
conflictCount
};
}
/**
* Parse `git branch --format=%(refname:short)\t%(HEAD)` output.
*
* @param output Git branch output.
* @returns Parsed {@link GitBranches}.
*/
function parseGitBranches(output) {
const branches = [];
let currentBranch;
const lines = output.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
for (const line of lines) {
const parts = line.split(" ");
const name = parts[0];
branches.push(name);
if (parts.length > 1 && parts[1] === "*") currentBranch = name;
}
return {
branches,
currentBranch
};
}
function isAuthFailure(err) {
if (!(err instanceof CommandExitError)) return false;
const message = `${err.stderr}\n${err.stdout}`.toLowerCase();
return [
"authentication failed",
"terminal prompts disabled",
"could not read username",
"invalid username or password",
"access denied",
"permission denied",
"not authorized"
].some((snippet) => message.includes(snippet));
}
function getScopeFlag(scope) {
if (scope !== "global" && scope !== "local" && scope !== "system") throw new InvalidArgumentError("Git config scope must be one of: global, local, system.");
return `--${scope}`;
}
function isMissingUpstream(err) {
if (!(err instanceof CommandExitError)) return false;
const message = `${err.stderr}\n${err.stdout}`.toLowerCase();
return [
"has no upstream branch",
"no upstream branch",
"no upstream configured",
"no tracking information for the current branch",
"no tracking information",
"set the remote as upstream",
"set the upstream branch",
"please specify which branch you want to merge with"
].some((snippet) => message.includes(snippet));
}
function buildAuthErrorMessage(action, missingPassword) {
if (missingPassword) return `Git ${action} requires a password/token for private repositories.`;
return `Git ${action} requires credentials for private repositories.`;
}
function buildUpstreamErrorMessage(action) {
if (action === "push") return "Git push failed because no upstream branch is configured. Set upstream once with { setUpstream: true } (and optional remote/branch), or pass remote and branch explicitly.";
return "Git pull failed because no upstream branch is configured. Pass remote and branch explicitly, or set upstream once (push with { setUpstream: true } or run: git branch --set-upstream-to=origin/<branch> <branch>).";
}
function getRepoPathForScope(scope, path) {
if (scope !== "local") return;
if (!path) throw new InvalidArgumentError("A repository path is required when using scope \"local\".");
return path;
}
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/objectWithoutPropertiesLoose.js
function _objectWithoutPropertiesLoose(r, e) {
if (null == r) return {};
var t = {};
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
if (e.includes(n)) continue;
t[n] = r[n];
}
return t;
}
//#endregion
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/objectWithoutProperties.js
function _objectWithoutProperties(e, t) {
if (null == e) return {};
var o, r, i = _objectWithoutPropertiesLoose(e, t);
if (Object.getOwnPropertySymbols) {
var s = Object.getOwnPropertySymbols(e);
for (r = 0; r < s.length; r++) o = s[r], t.includes(o) || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]);
}
return i;
}
//#endregion
//#region src/sandbox/git/index.ts
const _excluded$1 = [
"username",
"password",
"branch",
"depth",
"path",
"dangerouslyStoreCredentials"
];
const _excluded2 = ["bare", "initialBranch"];
const _excluded3 = ["fetch", "overwrite"];
const _excluded4 = ["force"];
const _excluded5 = ["files", "all"];
const _excluded6 = [
"authorName",
"authorEmail",
"allowEmpty"
];
const _excluded7 = [
"mode",
"target",
"paths"
];
const _excluded8 = [
"paths",
"staged",
"worktree",
"source"
];
const _excluded9 = [
"remote",
"branch",
"setUpstream",
"username",
"password"
];
const _excluded10 = [
"remote",
"branch",
"username",
"password"
];
const _excluded11 = [
"username",
"password",
"host",
"protocol"
];
const _excluded12 = ["envs"];
const _excluded13 = ["envs"];
const DEFAULT_GIT_ENV = { GIT_TERMINAL_PROMPT: "0" };
/**
* Module for running git operations in the sandbox.
*
* @deprecated Run git with `sandbox.commands.run()` instead. The git module will be removed in the next major version.
*/
var Git = class {
constructor(commands) {
this.commands = commands;
}
/**
* Clone a git repository into the sandbox.
*
* @param url Git repository URL.
* @param opts Clone options.
* @returns Command result from the command runner.
*/
async clone(url, opts) {
var _this = this;
const _ref = opts !== null && opts !== void 0 ? opts : {}, { username, password, branch, depth, path, dangerouslyStoreCredentials } = _ref, rest = _objectWithoutProperties(_ref, _excluded$1);
if (password && !username) throw new InvalidArgumentError("Username is required when using a password or token for git clone.");
const attemptClone = async (authUsername, authPassword) => {
const urlWithCreds = authUsername && authPassword ? withCredentials(url, authUsername, authPassword) : url;
const sanitizedUrl = stripCredentials(urlWithCreds);
const stripInlineCreds = !dangerouslyStoreCredentials && sanitizedUrl !== urlWithCreds;
const repoPath = stripInlineCreds ? path !== null && path !== void 0 ? path : deriveRepoDirFromUrl(url) : path;
if (stripInlineCreds && !repoPath) throw new InvalidArgumentError("A destination path is required when using credentials without storing them.");
const args = ["clone", urlWithCreds];
if (branch) args.push("--branch", branch, "--single-branch");
if (depth) args.push("--depth", depth.toString());
if (repoPath) args.push(repoPath);
const result = await _this.runGit(args, void 0, rest);
if (stripInlineCreds) await _this.runGit([
"remote",
"set-url",
"origin",
sanitizedUrl
], repoPath, rest);
return result;
};
try {
return await attemptClone(username, password);
} catch (err) {
if (isAuthFailure(err)) throw new GitAuthError(buildAuthErrorMessage("clone", Boolean(username) && !password));
throw err;
}
}
/**
* Initialize a new git repository.
*
* @param path Destination path for the repository.
* @param opts Init options.
* @returns Command result from the command runner.
*/
async init(path, opts) {
var _this2 = this;
const _ref2 = opts !== null && opts !== void 0 ? opts : {}, { bare, initialBranch } = _ref2, rest = _objectWithoutProperties(_ref2, _excluded2);
const args = ["init"];
if (initialBranch) args.push("--initial-branch", initialBranch);
if (bare) args.push("--bare");
args.push(path);
return _this2.runGit(args, void 0, rest);
}
/**
* Add (or update) a remote for a repository.
*
* @param path Repository path.
* @param name Remote name (for example, `"origin"`).
* @param url Remote URL.
* @param opts Remote add options.
* @returns Command result from the command runner.
*/
async remoteAdd(path, name, url, opts) {
var _this3 = this;
if (!name || !url) throw new InvalidArgumentError("Both remote name and URL are required to add a git remote.");
const _ref3 = opts !== null && opts !== void 0 ? opts : {}, { fetch, overwrite } = _ref3, rest = _objectWithoutProperties(_ref3, _excluded3);
const addArgs = ["remote", "add"];
if (fetch) addArgs.push("-f");
addArgs.push(name, url);
if (!overwrite) return _this3.runGit(addArgs, path, rest);
let cmd = `${buildGitCommand(addArgs, path)} || ${buildGitCommand([
"remote",
"set-url",
name,
url
], path)}`;
if (fetch) {
const fetchCmd = buildGitCommand(["fetch", name], path);
cmd = `(${cmd}) && ${fetchCmd}`;
}
return _this3.runShell(cmd, rest);
}
/**
* Get the URL for a git remote.
*
* Returns `undefined` when the remote does not exist.
*
* @param path Repository path.
* @param name Remote name (for example, `"origin"`).
* @param opts Command execution options.
* @returns Remote URL if present.
*/
async remoteGet(path, name, opts) {
var _this4 = this;
if (!name) throw new InvalidArgumentError("Remote name is required.");
const cmd = `${buildGitCommand([
"remote",
"get-url",
name
], path)} || true`;
const trimmed = (await _this4.runShell(cmd, opts)).stdout.trim();
return trimmed.length > 0 ? trimmed : void 0;
}
/**
* Get repository status information.
*
* @param path Repository path.
* @param opts Command execution options.
* @returns Parsed git status.
*/
async status(path, opts) {
return parseGitStatus((await this.runGit([
"status",
"--porcelain=1",
"-b"
], path, opts)).stdout);
}
/**
* List branches in a repository.
*
* @param path Repository path.
* @param opts Command execution options.
* @returns Parsed branch list.
*/
async branches(path, opts) {
return parseGitBranches((await this.runGit(["branch", "--format=%(refname:short) %(HEAD)"], path, opts)).stdout);
}
/**
* Create and check out a new branch.
*
* @param path Repository path.
* @param branch Branch name to create.
* @param opts Command execution options.
* @returns Command result from the command runner.
*/
async createBranch(path, branch, opts) {
return this.runGit([
"checkout",
"-b",
branch
], path, opts);
}
/**
* Check out an existing branch.
*
* @param path Repository path.
* @param branch Branch name to check out.
* @param opts Command execution options.
* @returns Command result from the command runner.
*/
async checkoutBranch(path, branch, opts) {
return this.runGit(["checkout", branch], path, opts);
}
/**
* Delete a branch.
*
* @param path Repository path.
* @param branch Branch name to delete.
* @param opts Delete options.
* @returns Command result from the command runner.
*/
async deleteBranch(path, branch, opts) {
var _this9 = this;
const _ref4 = opts !== null && opts !== void 0 ? opts : {}, { force } = _ref4, rest = _objectWithoutProperties(_ref4, _excluded4);
const args = [
"branch",
force ? "-D" : "-d",
branch
];
return _this9.runGit(args, path, rest);
}
/**
* Stage files for commit.
*
* @param path Repository path.
* @param opts Add options.
* @returns Command result from the command runner.
*/
async add(path, opts) {
var _this10 = this;
const _ref5 = opts !== null && opts !== void 0 ? opts : {}, { files, all = true } = _ref5, rest = _objectWithoutProperties(_ref5, _excluded5);
const args = ["add"];
if (!files || files.length === 0) args.push(all ? "-A" : ".");
else args.push("--", ...files);
return _this10.runGit(args, path, rest);
}
/**
* Create a commit in the repository.
*
* @param path Repository path.
* @param message Commit message.
* @param opts Commit options.
* @returns Command result from the command runner.
*/
async commit(path, message, opts) {
var _this11 = this;
const _ref6 = opts !== null && opts !== void 0 ? opts : {}, { authorName, authorEmail, allowEmpty } = _ref6, rest = _objectWithoutProperties(_ref6, _excluded6);
const args = [
"commit",
"-m",
message
];
if (allowEmpty) args.push("--allow-empty");
const authorArgs = [];
if (authorName) authorArgs.push("-c", `user.name=${authorName}`);
if (authorEmail) authorArgs.push("-c", `user.email=${authorEmail}`);
return _this11.runGit([...authorArgs, ...args], path, rest);
}
/**
* Reset the current HEAD to a specified state.
*
* @param path Repository path.
* @param opts Reset options.
* @returns Command result from the command runner.
*/
async reset(path, opts) {
var _this12 = this;
const _ref7 = opts !== null && opts !== void 0 ? opts : {}, { mode, target, paths } = _ref7, rest = _objectWithoutProperties(_ref7, _excluded7);
const allowedModes = [
"soft",
"mixed",
"hard",
"merge",
"keep"
];
if (mode && !allowedModes.includes(mode)) throw new InvalidArgumentError(`Reset mode must be one of ${allowedModes.join(", ")}.`);
const args = ["reset"];
if (mode) args.push(`--${mode}`);
if (target) args.push(target);
if (paths && paths.length > 0) args.push("--", ...paths);
return _this12.runGit(args, path, rest);
}
/**
* Restore working tree files or unstage changes.
*
* @param path Repository path.
* @param opts Restore options.
* @returns Command result from the command runner.
*/
async restore(path, opts) {
var _this13 = this;
const { paths, staged, worktree, source } = opts, rest = _objectWithoutProperties(opts, _excluded8);
if (!paths || paths.length === 0) throw new InvalidArgumentError("At least one path is required.");
let resolvedStaged = staged;
let resolvedWorktree = worktree;
if (staged === void 0 && worktree === void 0) resolvedWorktree = true;
else if (staged === true && worktree === void 0) resolvedWorktree = false;
else if (staged === void 0 && worktree !== void 0) resolvedStaged = false;
if (resolvedStaged === false && resolvedWorktree === false) throw new InvalidArgumentError("At least one of staged or worktree must be true.");
const args = ["restore"];
if (resolvedWorktree) args.push("--worktree");
if (resolvedStaged) args.push("--staged");
if (source) args.push("--source", source);
args.push("--", ...paths);
return _this13.runGit(args, path, rest);
}
/**
* Push commits to a remote.
*
* @param path Repository path.
* @param opts Push options.
* @returns Command result from the command runner.
*/
async push(path, opts) {
var _this14 = this;
const _ref8 = opts !== null && opts !== void 0 ? opts : {}, { remote, branch, setUpstream = true, username, password } = _ref8, rest = _objectWithoutProperties(_ref8, _excluded9);
if (password && !username) throw new InvalidArgumentError("Username is required when using a password or token for git push.");
if (username && password) {
const remoteName = await _this14.resolveRemoteName(path, remote, rest);
return _this14.withRemoteCredentials(path, remoteName, username, password, rest, () => _this14.runGit(buildPushArgs(remoteName, {
remote,
branch,
setUpstream
}), path, rest));
}
try {
return await _this14.runGit(buildPushArgs(void 0, {
remote,
branch,
setUpstream
}), path, rest);
} catch (err) {
if (isAuthFailure(err)) throw new GitAuthError(buildAuthErrorMessage("push", Boolean(username) && !password));
if (isMissingUpstream(err)) throw new GitUpstreamError(buildUpstreamErrorMessage("push"));
throw err;
}
}
/**
* Pull changes from a remote.
*
* @param path Repository path.
* @param opts Pull options.
* @returns Command result from the command runner.
*/
async pull(path, opts) {
var _this15 = this;
const _ref9 = opts !== null && opts !== void 0 ? opts : {}, { remote, branch, username, password } = _ref9, rest = _objectWithoutProperties(_ref9, _excluded10);
if (password && !username) throw new InvalidArgumentError("Username is required when using a password or token for git pull.");
if (!remote && !branch) {
if (!await _this15.hasUpstream(path, rest)) throw new GitUpstreamError(buildUpstreamErrorMessage("pull"));
}
const buildArgs = (remoteName) => {
const args = ["pull"];
const targetRemote = remoteName !== null && remoteName !== void 0 ? remoteName : remote;
if (targetRemote) args.push(targetRemote);
if (branch) args.push(branch);
return args;
};
if (username && password) {
const remoteName = await _this15.resolveRemoteName(path, remote, rest);
return _this15.withRemoteCredentials(path, remoteName, username, password, rest, () => _this15.runGit(buildArgs(remoteName), path, rest));
}
try {
return await _this15.runGit(buildArgs(), path, rest);
} catch (err) {
if (isAuthFailure(err)) throw new GitAuthError(buildAuthErrorMessage("pull", Boolean(username) && !password));
if (isMissingUpstream(err)) throw new GitUpstreamError(buildUpstreamErrorMessage("pull"));
throw err;
}
}
/**
* Set a git config value.
*
* Use `scope: "local"` together with `path` to configure a specific repository.
*
* @param key Git config key (for example, `"pull.rebase"`).
* @param value Git config value.
* @param opts Config options.
* @returns Command result from the command runner.
*/
async setConfig(key, value, opts) {
var _this16 = this;
var _opts$scope;
if (!key) throw new InvalidArgumentError("Git config key is required.");
const scope = (_opts$scope = opts === null || opts === void 0 ? void 0 : opts.scope) !== null && _opts$scope !== void 0 ? _opts$scope : "global";
const scopeFlag = getScopeFlag(scope);
const repoPath = getRepoPathForScope(scope, opts === null || opts === void 0 ? void 0 : opts.path);
return _this16.runGit([
"config",
scopeFlag,
key,
value
], repoPath, opts);
}
/**
* Get a git config value.
*
* Returns `undefined` when the key is not set in the requested scope.
*
* @param key Git config key (for example, `"pull.rebase"`).
* @param opts Config options.
* @returns The config value if present.
*/
async getConfig(key, opts) {
var _this17 = this;
var _opts$scope2;
if (!key) throw new InvalidArgumentError("Git config key is required.");
const scope = (_opts$scope2 = opts === null || opts === void 0 ? void 0 : opts.scope) !== null && _opts$scope2 !== void 0 ? _opts$scope2 : "global";
const scopeFlag = getScopeFlag(scope);
const repoPath = getRepoPathForScope(scope, opts === null || opts === void 0 ? void 0 : opts.path);
const cmd = `${buildGitCommand([
"config",
scopeFlag,
"--get",
key
], repoPath)} || true`;
const trimmed = (await _this17.runShell(cmd, opts)).stdout.trim();
return trimmed.length > 0 ? trimmed : void 0;
}
/**
* Dangerously authenticate git globally via the credential helper.
*
* This persists credentials in the credential store.
* Prefer short-lived credentials when possible.
*
* @param opts Authentication options.
* @returns Command result from the command runner.
*/
async dangerouslyAuthenticate(opts) {
var _this18 = this;
const { username, password, host, protocol } = opts, rest = _objectWithoutProperties(opts, _excluded11);
if (!username || !password) throw new InvalidArgumentError("Both username and password are required to authenticate git.");
const targetHost = (host !== null && host !== void 0 ? host : "github.com").trim();
const credentialInput = [
`protocol=${(protocol !== null && protocol !== void 0 ? protocol : "https").trim()}`,
`host=${targetHost}`,
`username=${username}`,
`password=${password}`,
"",
""
].join("\n");
await _this18.runGit([
"config",
"--global",
"credential.helper",
"store"
], void 0, rest);
const approveCmd = `printf %s ${shellQuote(credentialInput)} | ${buildGitCommand(["credential", "approve"])}`;
return _this18.runShell(approveCmd, rest);
}
/**
* Configure git user name and email.
*
* @param name Git user name.
* @param email Git user email.
* @param opts Config options.
* @returns Command result from the command runner.
*/
async configureUser(name, email, opts) {
var _this19 = this;
var _opts$scope3;
if (!name || !email) throw new InvalidArgumentError("Both name and email are required.");
const scope = (_opts$scope3 = opts === null || opts === void 0 ? void 0 : opts.scope) !== null && _opts$scope3 !== void 0 ? _opts$scope3 : "global";
const configOpts = _objectSpread2(_objectSpread2({}, opts), {}, { scope });
await _this19.setConfig("user.name", name, configOpts);
return _this19.setConfig("user.email", email, configOpts);
}
/**
* Build and execute a git command inside the sandbox.
*
* @param args Git arguments to pass to the git binary.
* @param repoPath Repository path used with `git -C`, if provided.
* @param opts Command execution options.
* @returns Command result from the command runner.
*/
async runGit(args, repoPath, opts) {
var _this20 = this;
const _ref10 = opts !== null && opts !== void 0 ? opts : {}, { envs } = _ref10, rest = _objectWithoutProperties(_ref10, _excluded12);
const cmd = buildGitCommand(args, repoPath);
const mergedEnvs = _objectSpread2(_objectSpread2({}, DEFAULT_GIT_ENV), envs !== null && envs !== void 0 ? envs : {});
return _this20.commands.run(cmd, _objectSpread2(_objectSpread2({}, rest), {}, { envs: mergedEnvs }));
}
/**
* Execute a raw shell command while applying default git environment variables.
*
Note: We can likely just modify runGit later to allow appending commands to the git but for now it's separate.
*/
async runShell(cmd, opts) {
var _this21 = this;
const _ref11 = opts !== null && opts !== void 0 ? opts : {}, { envs } = _ref11, rest = _objectWithoutProperties(_ref11, _excluded13);
const mergedEnvs = _objectSpread2(_objectSpread2({}, DEFAULT_GIT_ENV), envs !== null && envs !== void 0 ? envs : {});
return _this21.commands.run(cmd, _objectSpread2(_objectSpread2({}, rest), {}, { envs: mergedEnvs }));
}
async getRemoteUrl(path, remote, opts) {
const url = (await this.runGit([
"remote",
"get-url",
remote
], path, opts)).stdout.trim();
if (!url) throw new InvalidArgumentError(`Remote "${remote}" URL not found in repository.`);
return url;
}
async resolveRemoteName(path, remote, opts) {
var _this23 = this;
if (remote) return remote;
const remotes = (await _this23.runGit(["remote"], path, opts)).stdout.split("\n").map((line) => line.trim()).filter(Boolean);
if (remotes.length === 1) return remotes[0];
throw new InvalidArgumentError("Remote is required when using username/password and the repository has multiple remotes.");
}
async withRemoteCredentials(path, remote, username, password, opts, operation) {
var _this24 = this;
const originalUrl = await _this24.getRemoteUrl(path, remote, opts);
const credentialUrl = withCredentials(originalUrl, username, password);
await _this24.runGit([
"remote",
"set-url",
remote,
credentialUrl
], path, opts);
let result;
let operationError;
try {
result = await operation();
} catch (err) {
operationError = err;
}
let restoreError;
try {
await _this24.runGit([
"remote",
"set-url",
remote,
originalUrl
], path, opts);
} catch (err) {
restoreError = err;
}
if (operationError) throw operationError;
if (restoreError) throw restoreError;
return result;
}
async hasUpstream(path, opts) {
var _this25 = this;
try {
return (await _this25.runGit([
"rev-parse",
"--abbrev-ref",
"--symbolic-full-name",
"@{u}"
], path, opts)).stdout.trim().length > 0;
} catch (_unused) {
return false;
}
}
};
//#endregion
//#region src/volume/client.ts
const FILE_TIMEOUT_MS = 36e5;
var VolumeConnectionConfig = class VolumeConnectionConfig {
constructor(volume, opts) {
var _ref, _opts$debug, _opts$requestTimeoutM, _opts$httpVersion;
_defineProperty(this, "domain", void 0);
_defineProperty(this, "debug", void 0);
_defineProperty(this, "apiUrl", void 0);
_defineProperty(this, "token", void 0);
_defineProperty(this, "headers", void 0);
_defineProperty(this, "logger", void 0);
_defineProperty(this, "requestTimeoutMs", void 0);
_defineProperty(this, "signal", void 0);
_defineProperty(this, "proxy", void 0);
_defineProperty(this, "httpVersion", void 0);
this.domain = (opts === null || opts === void 0 ? void 0 : opts.domain) || volume.domain || VolumeConnectionConfig.domain;
this.debug = (_ref = (_opts$debug = opts === null || opts === void 0 ? void 0 : opts.debug) !== null && _opts$debug !== void 0 ? _opts$debug : volume.debug) !== null && _ref !== void 0 ? _ref : VolumeConnectionConfig.debug;
this.apiUrl = (opts === null || opts === void 0 ? void 0 : opts.apiUrl) || VolumeConnectionConfig.volumeApiUrl || (this.debug ? "http://localhost:8080" : `https://api.${this.domain}`);
this.token = (opts === null || opts === void 0 ? void 0 : opts.token) || volume.token;
this.headers = opts === null || opts === void 0 ? void 0 : opts.headers;
this.logger = opts === null || opts === void 0 ? void 0 : opts.logger;
this.requestTimeoutMs = (_opts$requestTimeoutM = opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs) !== null && _opts$requestTimeoutM !== void 0 ? _opts$requestTimeoutM : REQUEST_TIMEOUT_MS;
this.signal = opts === null || opts === void 0 ? void 0 : opts.signal;
this.proxy = (opts === null || opts === void 0 ? void 0 : opts.proxy) || volume.proxy;
this.httpVersion = resolveHttpVersion((_opts$httpVersion = opts === null || opts === void 0 ? void 0 : opts.httpVersion) !== null && _opts$httpVersion !== void 0 ? _opts$httpVersion : volume.httpVersion);
}
static get domain() {
return getEnvVar("E2B_DOMAIN") || "e2b.app";
}
static get debug() {
return (getEnvVar("E2B_DEBUG") || "false").toLowerCase() === "true";
}
static get volumeApiUrl() {
return getEnvVar("E2B_VOLUME_API_URL");
}
getSignal(requestTimeoutMs, signal) {
return buildRequestSignal(requestTimeoutMs !== null && requestTimeoutMs !== void 0 ? requestTimeoutMs : this.requestTimeoutMs, signal !== null && signal !== void 0 ? signal : this.signal);
}
};
/**
* Client for interacting with the E2B Volume API.
*/
var VolumeApiClient = class {
constructor(config) {
_defineProperty(this, "api", void 0);
this.api = (0, openapi_fetch.default)({
baseUrl: config.apiUrl,
fetch: createApiFetch(config),
headers: _objectSpread2(_objectSpread2(_objectSpread2({}, defaultHeaders), config.token && { Authorization: `Bearer ${config.token}` }), config.headers)
});
if (config.logger) this.api.use(createApiLogger(config.logger));
}
};
//#endregion
//#region src/volume/types.ts
/**
* File type enum.
*/
let VolumeFileType = /* @__PURE__ */ function(VolumeFileType) {
VolumeFileType["UNKNOWN"] = "unknown";
VolumeFileType["FILE"] = "file";
VolumeFileType["DIRECTORY"] = "directory";
VolumeFileType["SYMLINK"] = "symlink";
return VolumeFileType;
}({});
//#endregion
//#region src/volume/index.ts
/**
* Convert API VolumeEntryStat to SDK VolumeEntryStat.
*/
function convertVolumeEntryStat(entry) {
return _objectSpread2(_objectSpread2({}, entry), {}, {
type: entry.type,
atime: new Date(entry.atime),
mtime: new Date(entry.mtime),
ctime: new Date(entry.ctime)
});
}
/**
* Module for interacting with E2B volumes.
*
* Create a `Volume` instance to interact with a volume by its ID,
* or use the static methods to manage volumes.
*/
var Volume = class extends ClientFactory {
/**
* Create a local Volume instance with no API call.
*
* @param volumeId volume ID.
* @param name volume name.
* @param token volume auth token.
* @param domain domain for the volume API.
* @param debug whether to use debug mode.
* @param proxy proxy URL for the volume content API.
* @param opts connection options kept for the volume content API.
*/
constructor(volumeId, name, token, domain, debug, proxy, opts) {
super();
_defineProperty(this, "volumeId", void 0);
_defineProperty(this, "name", void 0);
_defineProperty(this, "token", void 0);
_defineProperty(this, "domain", void 0);
_defineProperty(this, "debug", void 0);
_defineProperty(this, "proxy", void 0);
_defineProperty(this, "httpVersion", void 0);
this.volumeId = volumeId;
this.name = name;
this.token = token;
this.domain = domain;
this.debug = debug;
this.proxy = proxy;
this.httpVersion = opts === null || opts === void 0 ? void 0 : opts.httpVersion;
}
/**
* Create a new volume.
*
* @param name name of the volume.
* @param opts connection options.
*
* @returns new Volume instance.
*/
static async create(name, opts) {
var _this = this;
const apiOpts = _this.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.POST("/volumes", {
body: { name },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
const err = handleApiError(res, VolumeError);
if (err) throw err;
if (!res.data) throw new Error("Response data is missing");
return new _this(res.data.volumeID, res.data.name, res.data.token, res.data.domain || config.domain, config.debug, config.proxy, { httpVersion: config.httpVersion });
}
/**
* Connect to an existing volume by ID.
*
* @param volumeId volume ID.
* @param opts connection options.
*
* @returns Volume instance.
*/
static async connect(volumeId, opts) {
var _this2 = this;
const apiOpts = _this2.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const { name, token, domain } = await _this2.getInfo(volumeId, apiOpts);
return new _this2(volumeId, name, token, domain !== null && domain !== void 0 ? domain : config.domain, config.debug, config.proxy, { httpVersion: config.httpVersion });
}
/**
* Get volume information.
*
* @param volumeId volume ID.
* @param opts connection options.
*
* @returns volume information.
*/
static async getInfo(volumeId, opts) {
const apiOpts = this.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.GET("/volumes/{volumeID}", {
params: { path: { volumeID: volumeId } },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (res.response.status === 404) throw new VolumeNotFoundError(`Volume ${volumeId} not found`);
const err = handleApiError(res, VolumeError);
if (err) throw err;
return {
volumeId: res.data.volumeID,
name: res.data.name,
token: res.data.token,
domain: res.data.domain || void 0
};
}
/**
* List all volumes.
*
* @param opts connection options.
*
* @returns list of volume information.
*/
static async list(opts) {
var _this4 = this;
var _res$data;
const apiOpts = _this4.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.GET("/volumes", { signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal) });
const err = handleApiError(res, VolumeError);
if (err) throw err;
return ((_res$data = res.data) !== null && _res$data !== void 0 ? _res$data : []).map((vol) => ({
volumeId: vol.volumeID,
name: vol.name
}));
}
/**
* Destroy a volume.
*
* @param volumeId volume ID.
* @param opts connection options.
*/
static async destroy(volumeId, opts) {
const apiOpts = this.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.DELETE("/volumes/{volumeID}", {
params: { path: { volumeID: volumeId } },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (res.response.status === 404) return false;
const err = handleApiError(res, VolumeError);
if (err) throw err;
return true;
}
/**
* List directory contents.
*
* @param path path to the directory.
* @param opts connection options.
* @param [opts.depth] number of layers deep to recurse into the directory (default: 1).
*
* @returns list of entries in the directory.
*/
async list(path, opts) {
var _this6 = this;
const config = new VolumeConnectionConfig(_this6, opts);
const res = await new VolumeApiClient(config).api.GET("/volumecontent/{volumeID}/dir", {
params: {
path: { volumeID: _this6.volumeId },
query: {
path,
depth: opts === null || opts === void 0 ? void 0 : opts.depth
}
},
signal: config.getSignal()
});
if (res.response.status === 404) throw new VolumePathNotFoundError(`Path ${path} not found`);
const err = handleApiError(res, VolumeError);
if (err) throw err;
return (Array.isArray(res.data) ? res.data : []).map(convertVolumeEntryStat);
}
/**
* Create a directory.
*
* @param path path to the directory to create.
* @param options directory creation options.
* @param opts connection options.
*/
async makeDir(path, opts) {
var _this7 = this;
const config = new VolumeConnectionConfig(_this7, opts);
const res = await new VolumeApiClient(config).api.POST("/volumecontent/{volumeID}/dir", {
params: {
path: { volumeID: _this7.volumeId },
query: {
path,
uid: opts === null || opts === void 0 ? void 0 : opts.uid,
gid: opts === null || opts === void 0 ? void 0 : opts.gid,
mode: opts === null || opts === void 0 ? void 0 : opts.mode,
force: opts === null || opts === void 0 ? void 0 : opts.force
}
},
signal: config.getSignal()
});
if (res.response.status === 404) throw new VolumePathNotFoundError(`Path ${path} not found`);
const err = handleApiError(res, VolumeError);
if (err) throw err;
if (!res.data) throw new Error("Response data is missing");
return convertVolumeEntryStat(res.data);
}
/**
* Get information about a file or directory.
*
* @param path path to the file or directory.
* @param opts connection options.
*
* @returns information about the entry.
*/
async getInfo(path, opts) {
var _this8 = this;
const config = new VolumeConnectionConfig(_this8, opts);
const res = await new VolumeApiClient(config).api.GET("/volumecontent/{volumeID}/path", {
params: {
path: { volumeID: _this8.volumeId },
query: { path }
},
signal: config.getSignal()
});
if (res.response.status === 404) throw new VolumePathNotFoundError(`Path ${path} not found`);
const err = handleApiError(res, VolumeError);
if (err) throw err;
if (!res.data) throw new Error("Response data is missing");
return convertVolumeEntryStat(res.data);
}
/**
* Check whether a file or directory exists.
*
* Uses {@link getInfo} under the hood. Returns `true` if the path exists,
* `false` if it does not (404). Other errors are rethrown.
*
* @param path path to the file or directory.
* @param opts connection options.
*
* @returns `true` if the path exists, `false` otherwise.
*/
async exists(path, opts) {
var _this9 = this;
try {
await _this9.getInfo(path, opts);
return true;
} catch (err) {
if (err instanceof VolumePathNotFoundError) return false;
throw err;
}
}
/**
* Update file or directory metadata.
*
* @param path path to the file or directory.
* @param metadata metadata to update (uid, gid, mode).
* @param opts connection options.
*
* @returns updated entry information.
*/
async updateMetadata(path, metadata, opts) {
var _this10 = this;
const config = new VolumeConnectionConfig(_this10, opts);
const res = await new VolumeApiClient(config).api.PATCH("/volumecontent/{volumeID}/path", {
params: {
path: { volumeID: _this10.volumeId },
query: { path }
},
body: {
uid: metadata.uid,
gid: metadata.gid,
mode: metadata.mode
},
signal: config.getSignal()
});
if (res.response.status === 404) throw new VolumePathNotFoundError(`Path ${path} not found`);
const err = handleApiError(res, VolumeError);
if (err) throw err;
if (!res.data) throw new Error("Response data is missing");
return convertVolumeEntryStat(res.data);
}
async readFile(path, opts) {
var _this11 = this;
var _opts$format, _opts$requestTimeoutM;
const format = (_opts$format = opts === null || opts === void 0 ? void 0 : opts.format) !== null && _opts$format !== void 0 ? _opts$format : "text";
const config = new VolumeConnectionConfig(_this11, _objectSpread2(_objectSpread2({}, opts), {}, { requestTimeoutMs: (_opts$requestTimeoutM = opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs) !== null && _opts$requestTimeoutM !== void 0 ? _opts$requestTimeoutM : FILE_TIMEOUT_MS }));
const client = new VolumeApiClient(config);
if (format === "stream") {
const { controller, clearStartTimeout, cleanup } = setupRequestController(config.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal);
try {
var _opts$streamIdleTimeo;
const res = await client.api.GET("/volumecontent/{volumeID}/file", {
params: {
path: { volumeID: _this11.volumeId },
query: { path }
},
parseAs: "stream",
signal: controller.signal
});
if (res.response.status === 404) {
if (res.response.body && !res.response.bodyUsed) await res.response.body.cancel().catch(() => {});
cleanup();
throw new VolumePathNotFoundError(`Path ${path} not found`);
}
const err = handleApiError(res, VolumeError);
if (err) {
if (res.response.body && !res.response.bodyUsed) await res.response.body.cancel().catch(() => {});
cleanup();
throw err;
}
return wrapStreamWithConnectionCleanup(res.data, {
clearStartTimeout,
cleanup,
controller,
idleTimeoutMs: (_opts$streamIdleTimeo = opts === null || opts === void 0 ? void 0 : opts.streamIdleTimeoutMs) !== null && _opts$streamIdleTimeo !== void 0 ? _opts$streamIdleTimeo : config.requestTimeoutMs
});
} catch (err) {
cleanup();
throw err;
}
}
const res = await client.api.GET("/volumecontent/{volumeID}/file", {
params: {
path: { volumeID: _this11.volumeId },
query: { path }
},
parseAs: format === "bytes" ? "arrayBuffer" : format,
signal: config.getSignal()
});
if (res.response.status === 404) throw new VolumePathNotFoundError(`Path ${path} not found`);
const err = handleApiError(res, VolumeError);
if (err) throw err;
if (format === "bytes") return isArrayBufferLike(res.data) ? new Uint8Array(res.data) : /* @__PURE__ */ new Uint8Array();
if (format === "text") return typeof res.data === "string" ? res.data : "";
return isBlobLike(res.data) ? res.data : new Blob([]);
}
/**
* Write content to a file.
*
* Writing to a file that doesn't exist creates the file.
*
* Writing to a file that already exists overwrites the file.
*
* @param path path to the file.
* @param data data to write to the file. Data can be a string, `ArrayBuffer`, `Uint8Array` (e.g. a Node.js `Buffer`), `Blob`, or `ReadableStream`. `ReadableStream` data is streamed to the API instead of being buffered in memory (a browser that can't stream request bodies throws `InvalidArgumentError`; pass a `Blob`).
* @param options file creation options.
* @param opts connection options.
*
* @returns information about the written file
*/
async writeFile(path, data, opts) {
var _this12 = this;
var _opts$requestTimeoutM2;
const config = new VolumeConnectionConfig(_this12, _objectSpread2(_objectSpread2({}, opts), {}, { requestTimeoutMs: (_opts$requestTimeoutM2 = opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs) !== null && _opts$requestTimeoutM2 !== void 0 ? _opts$requestTimeoutM2 : FILE_TIMEOUT_MS }));
const client = new VolumeApiClient(config);
const { body, streamed } = await toUploadBody(data);
const signal = streamed ? opts === null || opts === void 0 ? void 0 : opts.signal : config.getSignal();
const res = await client.api.PUT("/volumecontent/{volumeID}/file", _objectSpread2({
params: {
path: { volumeID: _this12.volumeId },
query: {
path,
uid: opts === null || opts === void 0 ? void 0 : opts.uid,
gid: opts === null || opts === void 0 ? void 0 : opts.gid,
mode: opts === null || opts === void 0 ? void 0 : opts.mode,
force: opts === null || opts === void 0 ? void 0 : opts.force
}
},
bodySerializer: () => body,
body: {},
headers: { "Content-Type": "application/octet-stream" },
signal
}, streamed && { duplex: "half" }));
if (res.response.status === 404) throw new VolumePathNotFoundError(`Path ${path} not found`);
const err = handleApiError(res, VolumeError);
if (err) throw err;
if (!res.data) throw new Error("Response data is missing");
return convertVolumeEntryStat(res.data);
}
/**
* Remove a file or directory.
*
* @param path path to the file or directory to remove.
* @param opts connection options.
*/
async remove(path, opts) {
var _this13 = this;
const config = new VolumeConnectionConfig(_this13, opts);
const res = await new VolumeApiClient(config).api.DELETE("/volumecontent/{volumeID}/path", {
params: {
path: { volumeID: _this13.volumeId },
query: { path }
},
signal: config.getSignal()
});
if (res.response.status === 404) throw new VolumePathNotFoundError(`Path ${path} not found`);
const err = handleApiError(res, VolumeError);
if (err) throw err;
}
};
//#endregion
//#region src/envd/streamingTransport.ts
function asyncIterableToReadableStream(iterable) {
const iterator = iterable[Symbol.asyncIterator]();
return new ReadableStream({
async pull(controller) {
try {
const { done, value } = await iterator.next();
if (done) controller.close();
else controller.enqueue(value);
} catch (error) {
controller.error(error);
}
},
async cancel(reason) {
var _iterator$return;
await ((_iterator$return = iterator.return) === null || _iterator$return === void 0 ? void 0 : _iterator$return.call(iterator, reason));
}
});
}
function readableStreamToAsyncIterable(stream) {
return { [Symbol.asyncIterator]() {
return _wrapAsyncGenerator(function* () {
if (!stream) return;
const reader = stream.getReader();
try {
while (true) {
const { done, value } = yield _awaitAsyncGenerator(reader.read());
if (done) return;
yield value;
}
} finally {
reader.releaseLock();
}
})();
} };
}
function createStreamingHttpClient(fetch) {
return async (request) => {
const response = await fetch(request.url, {
method: request.method,
headers: request.header,
body: request.body ? asyncIterableToReadableStream(request.body) : void 0,
signal: request.signal,
duplex: "half"
});
return {
status: response.status,
header: response.headers,
body: readableStreamToAsyncIterable(response.body),
trailer: new Headers()
};
};
}
function createClientStreamingTransport({ baseUrl, fetch, interceptors = [] }) {
return (0, _connectrpc_connect_protocol_connect.createTransport)({
baseUrl,
httpClient: createStreamingHttpClient(fetch),
useBinaryFormat: false,
interceptors,
acceptCompression: [],
sendCompression: null,
compressMinBytes: 1024,
readMaxBytes: 4294967295,
writeMaxBytes: 4294967295
});
}
//#endregion
//#region src/sandbox/commands/pty.ts
/**
* Module for interacting with PTYs (pseudo-terminals) in the sandbox.
*/
var Pty = class {
constructor(transport, envdApi, connectionConfig, streamingTransport) {
this.transport = transport;
this.envdApi = envdApi;
this.connectionConfig = connectionConfig;
_defineProperty(this, "rpc", void 0);
_defineProperty(this, "streamingRpc", void 0);
_defineProperty(this, "envdVersion", void 0);
_defineProperty(this, "checkHealth", void 0);
_defineProperty(this, "defaultPtyConnectionTimeout", 6e4);
this.rpc = (0, _connectrpc_connect.createClient)(Process, this.transport);
this.streamingRpc = (0, _connectrpc_connect.createClient)(Process, streamingTransport);
this.envdVersion = envdApi.version;
this.checkHealth = () => checkSandboxHealth(this.envdApi);
}
/**
* Create a new PTY (pseudo-terminal).
*
* @param opts options for creating the PTY.
*
* @returns handle to interact with the PTY.
*/
async create(opts) {
var _this = this;
var _opts$requestTimeoutM, _opts$envs, _envs$TERM, _envs$LANG, _envs$LC_ALL, _opts$timeoutMs;
const requestTimeoutMs = (_opts$requestTimeoutM = opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs) !== null && _opts$requestTimeoutM !== void 0 ? _opts$requestTimeoutM : _this.connectionConfig.requestTimeoutMs;
const envs = _objectSpread2({}, (_opts$envs = opts === null || opts === void 0 ? void 0 : opts.envs) !== null && _opts$envs !== void 0 ? _opts$envs : {});
envs.TERM = (_envs$TERM = envs.TERM) !== null && _envs$TERM !== void 0 ? _envs$TERM : "xterm-256color";
envs.LANG = (_envs$LANG = envs.LANG) !== null && _envs$LANG !== void 0 ? _envs$LANG : "C.UTF-8";
envs.LC_ALL = (_envs$LC_ALL = envs.LC_ALL) !== null && _envs$LC_ALL !== void 0 ? _envs$LC_ALL : "C.UTF-8";
const { controller, clearStartTimeout, cleanup } = setupRequestController(requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal);
const events = _this.rpc.start({
process: {
cmd: "/bin/bash",
args: ["-i", "-l"],
envs,
cwd: opts === null || opts === void 0 ? void 0 : opts.cwd
},
pty: { size: {
cols: opts.cols,
rows: opts.rows
} }
}, {
headers: _objectSpread2(_objectSpread2({}, authenticationHeader(_this.envdVersion, opts === null || opts === void 0 ? void 0 : opts.user)), {}, { [KEEPALIVE_PING_HEADER]: 50 .toString() }),
signal: controller.signal,
timeoutMs: (_opts$timeoutMs = opts === null || opts === void 0 ? void 0 : opts.timeoutMs) !== null && _opts$timeoutMs !== void 0 ? _opts$timeoutMs : _this.defaultPtyConnectionTimeout
});
try {
const pid = await handleProcessStartEvent(events);
clearStartTimeout();
return new CommandHandle(pid, cleanup, () => _this.kill(pid), events, void 0, void 0, opts.onData, void 0, void 0, _this.checkHealth);
} catch (err) {
cleanup();
throw await handleRpcErrorWithHealthCheck(err, _this.checkHealth);
}
}
/**
* Connect to a running PTY.
*
* @param pid process ID of the PTY to connect to. You can get the list of running PTYs using {@link Commands.list}.
* @param opts connection options.
*
* @returns handle to interact with the PTY.
*/
async connect(pid, opts) {
var _this2 = this;
var _opts$requestTimeoutM2, _opts$timeoutMs2;
const { controller, clearStartTimeout, cleanup } = setupRequestController((_opts$requestTimeoutM2 = opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs) !== null && _opts$requestTimeoutM2 !== void 0 ? _opts$requestTimeoutM2 : _this2.connectionConfig.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal);
const events = _this2.rpc.connect({ process: { selector: {
case: "pid",
value: pid
} } }, {
signal: controller.signal,
headers: { [KEEPALIVE_PING_HEADER]: 50 .toString() },
timeoutMs: (_opts$timeoutMs2 = opts === null || opts === void 0 ? void 0 : opts.timeoutMs) !== null && _opts$timeoutMs2 !== void 0 ? _opts$timeoutMs2 : _this2.defaultPtyConnectionTimeout
});
try {
const pid = await handleProcessStartEvent(events);
clearStartTimeout();
return new CommandHandle(pid, cleanup, () => _this2.kill(pid), events, void 0, void 0, opts === null || opts === void 0 ? void 0 : opts.onData, void 0, void 0, _this2.checkHealth);
} catch (err) {
cleanup();
throw await handleRpcErrorWithHealthCheck(err, _this2.checkHealth);
}
}
/**
* Send input to a PTY.
*
* @param pid process ID of the PTY.
* @param data input data to send to the PTY.
* @param opts connection options.
*/
async sendInput(pid, data, opts) {
var _this3 = this;
try {
await _this3.rpc.sendInput({
input: { input: {
case: "pty",
value: data
} },
process: { selector: {
case: "pid",
value: pid
} }
}, { signal: _this3.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal) });
} catch (err) {
throw await handleRpcErrorWithHealthCheck(err, _this3.checkHealth);
}
}
/**
* Open a persistent input stream to a PTY.
*
* Input sent through this stream is ordered relative to other `send` calls.
* Use this instead of {@link Pty.sendInput} when ordering matters; each
* `sendInput` call is a separate request. The stream sends keepalive messages
* while idle.
*
* In browsers, this requires fetch request-body streaming support (Chromium
* over HTTP/2).
*
* @param pid Process ID of the PTY.
* @param opts Optional stream timeout and abort signal. `timeoutMs` defaults to `STREAM_INPUT_TIMEOUT_MS` (60_000 milliseconds).
* @returns A handle whose `send()` method queues input in order and whose `close()` method ends the stream and waits for envd.
* @throws {NotFoundError} If the PTY does not exist, surfaced from `send()` or `close()`.
* @throws {TimeoutError} If the stream timeout expires, surfaced from `send()` or `close()`.
* @example
* ```ts
* const input = sandbox.pty.streamInput(pid)
* await input.send('echo hello\n')
* await input.close()
* ```
*/
streamInput(pid, opts = {}) {
return new CommandInputHandle(this.streamingRpc, pid, "pty", opts, this.checkHealth);
}
/**
* Resize PTY.
* Call this when the terminal window is resized and the number of columns and rows has changed.
*
* @param pid process ID of the PTY.
* @param size new size of the PTY.
* @param opts connection options.
*/
async resize(pid, size, opts) {
var _this4 = this;
try {
await _this4.rpc.update({
process: { selector: {
case: "pid",
value: pid
} },
pty: { size }
}, { signal: _this4.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal) });
} catch (err) {
throw await handleRpcErrorWithHealthCheck(err, _this4.checkHealth);
}
}
/**
* Kill a running PTY specified by process ID.
* It uses `SIGKILL` signal to kill the PTY.
*
* @param pid process ID of the PTY.
* @param opts connection options.
*
* @returns `true` if the PTY was killed, `false` if the PTY was not found.
*/
async kill(pid, opts) {
var _this5 = this;
return killProcess(_this5.rpc, pid, _this5.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal), _this5.checkHealth);
}
};
//#endregion
//#region src/sandbox/commands/index.ts
const DEFAULT_SHELL = [
"/bin/bash",
"-l",
"-c"
];
/**
* Module for starting and interacting with commands in the sandbox.
*/
var Commands = class {
constructor(transport, envdApi, connectionConfig, streamingTransport) {
this.envdApi = envdApi;
this.connectionConfig = connectionConfig;
_defineProperty(this, "rpc", void 0);
_defineProperty(this, "streamingRpc", void 0);
_defineProperty(this, "defaultProcessConnectionTimeout", 6e4);
_defineProperty(this, "envdVersion", void 0);
_defineProperty(this, "checkHealth", void 0);
this.rpc = (0, _connectrpc_connect.createClient)(Process, transport);
this.streamingRpc = (0, _connectrpc_connect.createClient)(Process, streamingTransport);
this.envdVersion = envdApi.version;
this.checkHealth = () => checkSandboxHealth(this.envdApi);
}
/**
* @hidden
* @internal
*/
get supportsStdinClose() {
return (0, compare_versions.compareVersions)(this.envdVersion, ENVD_ENVD_CLOSE) >= 0;
}
/**
* List all running commands and PTY sessions.
*
* @param opts connection options.
*
* @returns list of running commands and PTY sessions.
*/
async list(opts) {
var _this = this;
try {
return (await _this.rpc.list({}, { signal: _this.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal) })).processes.map((p) => _objectSpread2(_objectSpread2({ pid: p.pid }, p.tag && { tag: p.tag }), {}, {
args: p.config.args,
envs: p.config.envs,
cmd: p.config.cmd
}, p.config.cwd && { cwd: p.config.cwd }));
} catch (err) {
throw await handleRpcErrorWithHealthCheck(err, _this.checkHealth);
}
}
/**
* Send data to command stdin.
*
* @param pid process ID of the command. You can get the list of running commands using {@link Commands.list}.
* @param data data to send to the command.
* @param opts connection options.
*/
async sendStdin(pid, data, opts) {
var _this2 = this;
try {
const payload = typeof data === "string" ? new TextEncoder().encode(data) : data;
await _this2.rpc.sendInput({
process: { selector: {
case: "pid",
value: pid
} },
input: { input: {
case: "stdin",
value: payload
} }
}, { signal: _this2.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal) });
} catch (err) {
throw await handleRpcErrorWithHealthCheck(err, _this2.checkHealth);
}
}
/**
* Open a persistent input stream to a command.
*
* Input sent through this stream is ordered relative to other `send` calls.
* Use this instead of {@link Commands.sendStdin} when ordering matters; each
* `sendStdin` call is a separate request. The stream sends keepalive messages
* while idle.
*
* In browsers, this requires fetch request-body streaming support (Chromium
* over HTTP/2).
*
* @param pid Process ID of the command.
* @param opts Optional stream timeout and abort signal. `timeoutMs` defaults to `STREAM_INPUT_TIMEOUT_MS` (60_000 milliseconds).
* @returns A handle whose `send()` method queues input in order and whose `close()` method ends the stream and waits for envd.
* @throws {NotFoundError} If the command does not exist, surfaced from `send()` or `close()`.
* @throws {TimeoutError} If the stream timeout expires, surfaced from `send()` or `close()`.
* @example
* ```ts
* const input = sandbox.commands.streamInput(pid)
* await input.send('hello\n')
* await input.close()
* ```
*/
streamInput(pid, opts = {}) {
return new CommandInputHandle(this.streamingRpc, pid, "stdin", opts, this.checkHealth);
}
/**
* Close command stdin.
*
* This signals EOF to the command. The command must have been started with `stdin: true`.
*
* @param pid process ID of the command. You can get the list of running commands using {@link Commands.list}.
* @param opts connection options.
*/
async closeStdin(pid, opts) {
var _this3 = this;
if (!_this3.supportsStdinClose) throw new SandboxError(`Sandbox envd version ${_this3.envdVersion} doesn't support closeStdin. Please rebuild your template to pick up the latest sandbox version.`);
try {
await _this3.rpc.closeStdin({ process: { selector: {
case: "pid",
value: pid
} } }, { signal: _this3.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal) });
} catch (err) {
throw await handleRpcErrorWithHealthCheck(err, _this3.checkHealth);
}
}
/**
* Kill a running command specified by its process ID.
* It uses `SIGKILL` signal to kill the command.
*
* @param pid process ID of the command. You can get the list of running commands using {@link Commands.list}.
* @param opts connection options.
*
* @returns `true` if the command was killed, `false` if the command was not found.
*/
async kill(pid, opts) {
var _this4 = this;
return killProcess(_this4.rpc, pid, _this4.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal), _this4.checkHealth);
}
/**
* Connect to a running command.
* You can use {@link CommandHandle.wait} to wait for the command to finish and get execution results.
*
* @param pid process ID of the command to connect to. You can get the list of running commands using {@link Commands.list}.
* @param opts connection options.
*
* @returns `CommandHandle` handle to interact with the running command.
*/
async connect(pid, opts) {
var _this5 = this;
var _opts$requestTimeoutM, _opts$timeoutMs;
const { controller, clearStartTimeout, cleanup } = setupRequestController((_opts$requestTimeoutM = opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs) !== null && _opts$requestTimeoutM !== void 0 ? _opts$requestTimeoutM : _this5.connectionConfig.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal);
const events = _this5.rpc.connect({ process: { selector: {
case: "pid",
value: pid
} } }, {
signal: controller.signal,
headers: { [KEEPALIVE_PING_HEADER]: 50 .toString() },
timeoutMs: (_opts$timeoutMs = opts === null || opts === void 0 ? void 0 : opts.timeoutMs) !== null && _opts$timeoutMs !== void 0 ? _opts$timeoutMs : _this5.defaultProcessConnectionTimeout
});
return _this5.createHandle(events, clearStartTimeout, cleanup, opts);
}
async run(cmd, opts) {
const proc = await this.start(cmd, opts);
return (opts === null || opts === void 0 ? void 0 : opts.background) ? proc : proc.wait();
}
async start(cmd, opts) {
var _this7 = this;
var _opts$shell, _opts$requestTimeoutM2, _opts$timeoutMs2;
if ((opts === null || opts === void 0 ? void 0 : opts.stdin) === false && (0, compare_versions.compareVersions)(_this7.envdVersion, "0.3.0") < 0) throw new SandboxError(`Sandbox envd version ${_this7.envdVersion} can't specify stdin, it's always turned on. Please rebuild your template if you need this feature.`);
const shellOpt = (_opts$shell = opts === null || opts === void 0 ? void 0 : opts.shell) !== null && _opts$shell !== void 0 ? _opts$shell : DEFAULT_SHELL;
if (!Array.isArray(shellOpt) || !shellOpt[0]) throw new InvalidArgumentError("`shell` must start with the executable, e.g. `['/bin/sh', '-c']`. Omit `shell` to use the default `['/bin/bash', '-l', '-c']`.");
const [shell, ...shellArgs] = shellOpt;
const { controller, clearStartTimeout, cleanup } = setupRequestController((_opts$requestTimeoutM2 = opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs) !== null && _opts$requestTimeoutM2 !== void 0 ? _opts$requestTimeoutM2 : _this7.connectionConfig.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal);
const events = _this7.rpc.start({
process: {
cmd: shell,
cwd: opts === null || opts === void 0 ? void 0 : opts.cwd,
envs: opts === null || opts === void 0 ? void 0 : opts.envs,
args: [...shellArgs, cmd]
},
stdin: (opts === null || opts === void 0 ? void 0 : opts.stdin) || false
}, {
headers: _objectSpread2(_objectSpread2({}, authenticationHeader(_this7.envdVersion, opts === null || opts === void 0 ? void 0 : opts.user)), {}, { [KEEPALIVE_PING_HEADER]: 50 .toString() }),
signal: controller.signal,
timeoutMs: (_opts$timeoutMs2 = opts === null || opts === void 0 ? void 0 : opts.timeoutMs) !== null && _opts$timeoutMs2 !== void 0 ? _opts$timeoutMs2 : _this7.defaultProcessConnectionTimeout
});
return _this7.createHandle(events, clearStartTimeout, cleanup, opts);
}
async createHandle(events, clearStartTimeout, cleanup, opts) {
var _this8 = this;
try {
var _opts$retainOutput;
const pid = await handleProcessStartEvent(events);
clearStartTimeout();
return new CommandHandle(pid, cleanup, () => _this8.kill(pid), events, opts === null || opts === void 0 ? void 0 : opts.onStdout, opts === null || opts === void 0 ? void 0 : opts.onStderr, void 0, (data, stdinOpts) => _this8.sendStdin(pid, data, stdinOpts), (stdinOpts) => _this8.closeStdin(pid, stdinOpts), _this8.checkHealth, (_opts$retainOutput = opts === null || opts === void 0 ? void 0 : opts.retainOutput) !== null && _opts$retainOutput !== void 0 ? _opts$retainOutput : true);
} catch (err) {
cleanup();
throw await handleRpcErrorWithHealthCheck(err, _this8.checkHealth);
}
}
};
//#endregion
//#region src/sandbox/iam.ts
/**
* Characters a workload token name cannot carry.
*
* The egress proxy reads a placeholder as everything between
* `'${e2b.identity.tokens.'` and the next `}`, then looks that name up in the
* registered tokens. A brace in the name breaks that in both directions: `}`
* ends the placeholder early, so `'a}b'` resolves the unrelated token `'a'` and
* leaves `'b}'` as literal text, and `{` lets a name close its own placeholder
* and open another one, minting a token the caller never referenced. Control
* characters are rejected separately because they cannot appear in an HTTP
* header value at all — the API would answer with an opaque 400.
*/
const INVALID_IAM_TOKEN_NAME_CHARS = new RegExp("[{}\\p{Cc}]", "u");
/**
* Properties the language and the runtime read off any object they serialize,
* await, or coerce to a string. A token is never named after them, so reading
* one cannot throw — otherwise `JSON.stringify(iam.tokens)` inside a callback
* would.
*/
const RUNTIME_PROBED_PROPS = /* @__PURE__ */ new Set([
"toJSON",
"then",
"toString",
"valueOf"
]);
/**
* Stand-in for a runtime-probed name that is not a registered token: it answers
* the probe, and `resolve` decides what using it as a token does — a probe never
* coerces or serializes what it reads, a token reference does one or the other.
*/
function runtimeProbeValue(prop, tokens, resolve) {
const value = prop === "toString" ? () => Object.prototype.toString.call(tokens()) : prop === "valueOf" ? () => tokens() : {};
Object.defineProperty(value, Symbol.toPrimitive, { value: resolve });
return Object.defineProperty(value, "toJSON", {
value: resolve,
enumerable: true
});
}
/**
* Reject a token name that cannot survive the placeholder grammar.
*
* @param name workload token name.
*
* @throws {@link InvalidArgumentError} if the name is empty or carries a brace
* or control character.
*/
function validateIamTokenName(name) {
if (name.length === 0 || INVALID_IAM_TOKEN_NAME_CHARS.test(name)) throw new InvalidArgumentError(`iam token name ${JSON.stringify(name)} is not usable: a token name cannot be empty or contain '{', '}' or control characters, because it is interpolated into the '\${e2b.identity.tokens.<name>}' placeholder the egress proxy resolves.`);
}
/**
* The placeholder the egress proxy replaces with a freshly minted token. The
* spelling is fixed by the backend: a placeholder can only select a persisted
* named token, never an inline audience or claim.
*/
function iamTokenPlaceholder(name) {
validateIamTokenName(name);
return `\${e2b.identity.tokens.${name}}`;
}
/**
* Token name to placeholder map, as exposed to a network `transform` callback.
*
* `tokenNames` are the workload tokens the request registers. Referencing any
* other name throws: the proxy never turns an unregistered name into a token, so
* a typo would surface as a confusing auth failure at the destination instead of
* an error here.
*
* `validate: false` is for the update-network endpoint, whose payload carries no
* `iam` config — the sandbox's registered token names are not known client-side
* there, so any name resolves to its placeholder.
*/
function iamTokenPlaceholders(tokenNames, { validate }) {
const tokens = {};
for (const name of tokenNames) tokens[name] = iamTokenPlaceholder(name);
/** Placeholder when names cannot be checked, otherwise the guard's error. */
const resolveUnregistered = (prop) => {
if (!validate) return iamTokenPlaceholder(prop);
throw new InvalidArgumentError(`Network transform references iam token '${prop}', which is not registered. ${tokenNames.length === 0 ? `Pass it to Sandbox.create as iam: { tokens: { '${prop}': Secret.iamToken({ audience, tokenType }) } }.` : `Registered tokens: ${tokenNames.map((name) => `'${name}'`).join(", ")}.`}`);
};
const proxy = new Proxy(tokens, {
get(target, prop, receiver) {
if (typeof prop === "string" && !Object.hasOwn(target, prop)) {
if (RUNTIME_PROBED_PROPS.has(prop)) return runtimeProbeValue(prop, () => proxy, () => resolveUnregistered(prop));
return resolveUnregistered(prop);
}
return Reflect.get(target, prop, receiver);
},
has(target, prop) {
return Object.hasOwn(target, prop);
}
});
return proxy;
}
/**
* Default per-request timeout (in milliseconds) for the file-upload phase
* (PUT to S3 presigned URL) when the caller hasn't supplied
* `requestTimeoutMs`. Large archives can take well over the 60s API
* default, so we use a generous 1-hour bound here.
* @internal
*/
const FILE_UPLOAD_TIMEOUT_MS = 36e5;
//#endregion
//#region src/template/logger.ts
/**
* Represents a single log entry from the template build process.
*/
var LogEntry = class {
constructor(timestamp, level, message) {
_defineProperty(this, "timestamp", void 0);
_defineProperty(this, "level", void 0);
_defineProperty(this, "message", void 0);
this.timestamp = timestamp;
this.level = level;
this.message = stripAnsi(message);
}
toString() {
return `[${this.timestamp.toISOString()}] [${this.level}] ${this.message}`;
}
};
/**
* Special log entry indicating the start of a build process.
*/
var LogEntryStart = class extends LogEntry {
constructor(timestamp, message) {
super(timestamp, "debug", message);
}
};
/**
* Special log entry indicating the end of a build process.
*/
var LogEntryEnd = class extends LogEntry {
constructor(timestamp, message) {
super(timestamp, "debug", message);
}
};
/**
* Interval in milliseconds for updating the build timer display.
* @internal
*/
const TIMER_UPDATE_INTERVAL_MS = 150;
/**
* Default minimum log level to display.
* @internal
*/
const DEFAULT_LEVEL = "info";
/**
* Colored labels for each log level.
* @internal
*/
const levels = {
error: chalk.default.red("ERROR"),
warn: chalk.default.hex("#FF4400")("WARN "),
info: chalk.default.hex("#FF8800")("INFO "),
debug: chalk.default.gray("DEBUG")
};
/**
* Numeric ordering of log levels for comparison (lower = less severe).
* @internal
*/
const level_order = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
var DefaultBuildLogger = class {
constructor(minLevel) {
_defineProperty(this, "minLevel", void 0);
_defineProperty(this, "state", void 0);
this.minLevel = minLevel !== null && minLevel !== void 0 ? minLevel : DEFAULT_LEVEL;
this.state = this.getInitialState();
}
logger(logEntry) {
if (logEntry instanceof LogEntryStart) {
this.startTimer();
return;
}
if (logEntry instanceof LogEntryEnd) {
clearInterval(this.state.timerInterval);
return;
}
if (level_order[logEntry.level] < level_order[this.minLevel]) return;
const formattedLine = this.formatLogLine(logEntry);
process.stdout.write(`${formattedLine}\n`);
this.updateTimer();
}
getInitialState(timerInterval) {
return {
startTime: Date.now(),
animationFrame: 0,
timerInterval
};
}
formatTimerLine() {
return `${((Date.now() - this.state.startTime) / 1e3).toFixed(1)}s`;
}
animateStatus() {
const frames = [
"⣾",
"⣽",
"⣻",
"⢿",
"⡿",
"⣟",
"⣯",
"⣷"
];
return `${frames[this.state.animationFrame % frames.length]}`;
}
formatLogLine(line) {
return `${this.formatTimerLine().padEnd(5)} | ${chalk.default.dim(line.timestamp.toLocaleTimeString(void 0, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
}))} ${levels[line.level] || levels[DEFAULT_LEVEL]} ${line.message}`;
}
startTimer() {
if (!process.stdout.isTTY) return;
const timerInterval = setInterval(this.updateTimer.bind(this), TIMER_UPDATE_INTERVAL_MS);
this.state = this.getInitialState(timerInterval);
this.updateTimer();
}
updateTimer() {
if (!process.stdout.isTTY) return;
this.state.animationFrame++;
const jumpingSquares = this.animateStatus();
process.stdout.write(`${jumpingSquares} Building ${this.formatTimerLine()}\r`);
}
};
/**
* Create a default build logger with animated timer display.
*
* @param options Logger configuration options
* @param options.minLevel Minimum log level to display (default: 'info')
* @returns Logger function that accepts LogEntry instances
*
* @example
* ```ts
* import { Template, defaultBuildLogger } from 'e2b'
*
* const template = Template().fromPythonImage()
*
* await Template.build(template, {
* alias: 'my-template',
* onBuildLogs: defaultBuildLogger({ minLevel: 'debug' })
* })
* ```
*/
function defaultBuildLogger(options) {
const buildLogger = new DefaultBuildLogger(options === null || options === void 0 ? void 0 : options.minLevel);
return buildLogger.logger.bind(buildLogger);
}
//#endregion
//#region ../../node_modules/.pnpm/error-stack-parser-es@2.0.1/node_modules/error-stack-parser-es/dist/lite.mjs
const FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+:\d+/;
const CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
const SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code\])?$/;
/**
* Given an Error object, extract the most information from it.
*
* @param {Error} error object
* @param {ParseOptions} options
* @return {Array} of StackFrames
*/
function parse$1(error, options) {
if (typeof error.stacktrace !== "undefined" || typeof error["opera#sourceloc"] !== "undefined") return parseOpera(error, options);
else if (error.stack && CHROME_IE_STACK_REGEXP.test(error.stack)) return parseV8OrIE(error, options);
else if (error.stack) return parseFFOrSafari(error, options);
else if (options === null || options === void 0 ? void 0 : options.allowEmpty) return [];
else throw new Error("Cannot parse given Error object");
}
/**
* Separate line and column numbers from a string of the form: (URI:Line:Column)
*/
function extractLocation(urlLike) {
if (!urlLike.includes(":")) return [
urlLike,
void 0,
void 0
];
const parts = /(.+?)(?::(\d+))?(?::(\d+))?$/.exec(urlLike.replace(/[()]/g, ""));
return [
parts[1],
parts[2] || void 0,
parts[3] || void 0
];
}
function applySlice(lines, options) {
if (options && options.slice != null) {
if (Array.isArray(options.slice)) return lines.slice(options.slice[0], options.slice[1]);
return lines.slice(0, options.slice);
}
return lines;
}
function parseV8OrIE(error, options) {
return parseV8OrIeString(error.stack, options);
}
function parseV8OrIeString(stack, options) {
return applySlice(stack.split("\n").filter((line) => {
return !!line.match(CHROME_IE_STACK_REGEXP);
}), options).map((line) => {
if (line.includes("(eval ")) line = line.replace(/eval code/g, "eval").replace(/(\(eval at [^()]*)|(,.*$)/g, "");
let sanitizedLine = line.replace(/^\s+/, "").replace(/\(eval code/g, "(").replace(/^.*?\s+/, "");
const location = sanitizedLine.match(/ (\(.+\)$)/);
sanitizedLine = location ? sanitizedLine.replace(location[0], "") : sanitizedLine;
const locationParts = extractLocation(location ? location[1] : sanitizedLine);
return {
function: location && sanitizedLine || void 0,
file: ["eval", "<anonymous>"].includes(locationParts[0]) ? void 0 : locationParts[0],
line: locationParts[1] ? +locationParts[1] : void 0,
col: locationParts[2] ? +locationParts[2] : void 0,
raw: line
};
});
}
function parseFFOrSafari(error, options) {
return parseFFOrSafariString(error.stack, options);
}
function parseFFOrSafariString(stack, options) {
return applySlice(stack.split("\n").filter((line) => {
return !line.match(SAFARI_NATIVE_CODE_REGEXP);
}), options).map((line) => {
if (line.includes(" > eval")) line = line.replace(/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g, ":$1");
if (!line.includes("@") && !line.includes(":")) return { function: line };
else {
const functionNameRegex = /(([^\n\r"\u2028\u2029]*".[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*(?:@[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*)*(?:[\n\r\u2028\u2029][^@]*)?)?[^@]*)@/;
const matches = line.match(functionNameRegex);
const functionName = matches && matches[1] ? matches[1] : void 0;
const locationParts = extractLocation(line.replace(functionNameRegex, ""));
return {
function: functionName,
file: locationParts[0],
line: locationParts[1] ? +locationParts[1] : void 0,
col: locationParts[2] ? +locationParts[2] : void 0,
raw: line
};
}
});
}
function parseOpera(e, options) {
if (!e.stacktrace || e.message.includes("\n") && e.message.split("\n").length > e.stacktrace.split("\n").length) return parseOpera9(e);
else if (!e.stack) return parseOpera10(e);
else return parseOpera11(e, options);
}
function parseOpera9(e, options) {
const lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
const lines = e.message.split("\n");
const result = [];
for (let i = 2, len = lines.length; i < len; i += 2) {
const match = lineRE.exec(lines[i]);
if (match) result.push({
file: match[2],
line: +match[1],
raw: lines[i]
});
}
return applySlice(result, options);
}
function parseOpera10(e, options) {
const lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
const lines = e.stacktrace.split("\n");
const result = [];
for (let i = 0, len = lines.length; i < len; i += 2) {
const match = lineRE.exec(lines[i]);
if (match) result.push({
function: match[3] || void 0,
file: match[2],
line: match[1] ? +match[1] : void 0,
raw: lines[i]
});
}
return applySlice(result, options);
}
function parseOpera11(error, options) {
return applySlice(error.stack.split("\n").filter((line) => {
return !!line.match(FIREFOX_SAFARI_STACK_REGEXP) && !line.match(/^Error created at/);
}), options).map((line) => {
const tokens = line.split("@");
const locationParts = extractLocation(tokens.pop());
const functionCall = tokens.shift() || "";
const functionName = functionCall.replace(/<anonymous function(: (\w+))?>/, "$2").replace(/\([^)]*\)/g, "") || void 0;
let argsRaw;
if (/\([^)]*\)/.test(functionCall)) argsRaw = functionCall.replace(/^[^(]+\(([^)]*)\)$/, "$1");
return {
function: functionName,
args: argsRaw === void 0 || argsRaw === "[arguments not available]" ? void 0 : argsRaw.split(","),
file: locationParts[0],
line: locationParts[1] ? +locationParts[1] : void 0,
col: locationParts[2] ? +locationParts[2] : void 0,
raw: line
};
});
}
//#endregion
//#region ../../node_modules/.pnpm/error-stack-parser-es@2.0.1/node_modules/error-stack-parser-es/dist/index.mjs
function stackframesLiteToStackframes(liteStackframes) {
return liteStackframes.map((liteStackframe) => {
return {
functionName: liteStackframe.function,
args: liteStackframe.args,
fileName: liteStackframe.file,
lineNumber: liteStackframe.line,
columnNumber: liteStackframe.col,
source: liteStackframe.raw
};
});
}
/**
* Given an Error object, extract the most information from it.
*
* @param {Error} error object
* @return {Array} of StackFrames
*/
function parse(error, options) {
return stackframesLiteToStackframes(parse$1(error, options));
}
//#endregion
//#region src/template/utils.ts
const _excluded = ["alias"];
/**
* Validate that a source path for copy operations is a relative path that stays
* within the context directory. This prevents path traversal attacks and ensures
* files are copied from within the expected directory.
*
* @param src The source path to validate
* @param stackTrace Optional stack trace for error reporting
* @throws TemplateError if the path is absolute or escapes the context directory
*
* Invalid paths:
* - Absolute paths: /absolute/path, C:\Windows\path
* - Parent directory escapes: ../foo, foo/../../bar, ./foo/../../../bar
*
* Valid paths:
* - Simple relative: foo, foo/bar
* - Current directory prefix: ./foo, ./foo/bar
* - Internal parent refs that don't escape: foo/../bar (stays within context)
*/
function validateRelativePath(src, stackTrace) {
if (node_path.default.isAbsolute(src)) throw new TemplateError(`Invalid source path "${src}": absolute paths are not allowed. Use a relative path within the context directory.`, stackTrace);
const normalized = node_path.default.normalize(src);
if (normalized === ".." || normalized.startsWith(".." + node_path.default.sep)) throw new TemplateError(`Invalid source path "${src}": path escapes the context directory. The path must stay within the context directory.`, stackTrace);
}
/**
* Normalize build arguments from different overload signatures.
* Handles string name or legacy options object with alias.
*
* @param nameOrOptions Name or legacy options with alias
* @param options Optional build options (when first arg is name)
* @returns Object with normalized name, tags, and build options
* @throws TemplateError if no template name is provided
*/
function normalizeBuildArguments(nameOrOptions, options) {
let name;
let buildOptions;
if (typeof nameOrOptions === "string") {
name = nameOrOptions;
buildOptions = options !== null && options !== void 0 ? options : {};
} else {
const { alias } = nameOrOptions, restOpts = _objectWithoutProperties(nameOrOptions, _excluded);
name = alias;
buildOptions = restOpts;
}
if (!name || name.length === 0) throw new TemplateError("Name must be provided");
return {
name,
buildOptions
};
}
/**
* Read and parse a .dockerignore file.
*
* @param contextPath Directory path containing the .dockerignore file
* @returns Array of ignore patterns (empty lines and comments are filtered out)
*/
function readDockerignore(contextPath) {
const dockerignorePath = node_path.default.join(contextPath, ".dockerignore");
if (!node_fs.default.existsSync(dockerignorePath)) return [];
return node_fs.default.readFileSync(dockerignorePath, "utf-8").replace(/^\uFEFF/, "").split("\n").filter((line) => !line.startsWith("#")).map((line) => line.trim()).filter((line) => line);
}
/**
* Normalize path separators to forward slashes for glob patterns (glob expects / even on Windows)
* @param path - The path to normalize
* @returns The normalized path
*/
function normalizePath(path) {
return path.replace(/\\/g, "/");
}
function normalizeIgnorePattern(pattern, contextPath) {
let trimmed = pattern.trim();
const negated = trimmed.startsWith("!");
if (negated) trimmed = trimmed.slice(1).trim();
if (node_path.default.isAbsolute(trimmed)) {
const relative = node_path.default.relative(contextPath, trimmed);
if (relative !== "" && relative !== ".." && !relative.startsWith(`..${node_path.default.sep}`) && !node_path.default.isAbsolute(relative)) trimmed = relative;
}
return (negated ? "!" : "") + trimmed;
}
/**
* Create a glob `ignore` matcher that follows `.dockerignore` semantics:
* patterns are relative to the context root, a pattern matching a directory
* excludes everything under it, and `!` patterns re-include paths
* (the last matching pattern wins). The context root is never excluded.
*
* @param ignorePatterns Ignore patterns in `.dockerignore` syntax
* @param contextPath Base directory the patterns are relative to
* @returns Matcher to pass as the glob `ignore` option
*/
function createIgnoreMatcher(ignorePatterns, contextPath) {
const absoluteContextPath = node_path.default.resolve(contextPath);
let matcher;
try {
matcher = new _e2b_dockerfile_utils.PatternMatcher(ignorePatterns.map((pattern) => normalizeIgnorePattern(pattern, absoluteContextPath)), { backslashIsSeparator: node_path.default.sep === "\\" });
} catch (err) {
throw new TemplateError(err.message);
}
const ignored = (p) => {
const relativePath = p.relativePosix();
return relativePath !== "" && matcher.matches(relativePath);
};
return {
ignored,
childrenIgnored: (p) => ignored(p) && !matcher.mayMatchUnder(p.relativePosix())
};
}
/**
* Get all files for a given path and ignore patterns.
*
* @param src Path to the source directory
* @param contextPath Base directory for resolving relative paths
* @param ignorePatterns Ignore patterns in `.dockerignore` syntax
* @returns Array of files
*/
async function getAllFilesInPath(src, contextPath, ignorePatterns, includeDirectories = true) {
const { glob } = await dynamicImport("glob");
const files = /* @__PURE__ */ new Map();
const ignore = createIgnoreMatcher(ignorePatterns, contextPath);
const globFiles = await glob(src, {
ignore,
withFileTypes: true,
dot: true,
cwd: contextPath
});
const depth = (p) => {
const relativePath = p.relativePosix();
return relativePath === "" ? 0 : relativePath.split("/").length;
};
globFiles.sort((a, b) => depth(a) - depth(b));
const walkedDirs = /* @__PURE__ */ new Set();
const isUnderWalkedDir = (relativePath) => {
if (walkedDirs.has("")) return true;
for (let parent = node_path.default.posix.dirname(relativePath); parent !== "." && parent !== "/"; parent = node_path.default.posix.dirname(parent)) if (walkedDirs.has(parent)) return true;
return false;
};
for (const file of globFiles) {
if (isUnderWalkedDir(file.relativePosix())) continue;
if (file.isDirectory()) {
walkedDirs.add(file.relativePosix());
if (includeDirectories) files.set(file.fullpath(), file);
(await glob(normalizePath(node_path.default.join(file.relative() || ".", "**/*")), {
ignore,
withFileTypes: true,
dot: true,
cwd: contextPath
})).forEach((f) => files.set(f.fullpath(), f));
} else files.set(file.fullpath(), file);
}
return Array.from(files.values()).sort((a, b) => a.fullpath() < b.fullpath() ? -1 : a.fullpath() > b.fullpath() ? 1 : 0);
}
const HASH_CHUNK_SIZE = 1048576;
/**
* Feed a file's content into a hash in fixed-size chunks, so large files
* are never loaded into memory at once.
*/
function hashFileContent(hash, filePath, buffer) {
const fd = node_fs.default.openSync(filePath, "r");
try {
let bytesRead;
while ((bytesRead = node_fs.default.readSync(fd, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, bytesRead));
} finally {
node_fs.default.closeSync(fd);
}
}
/**
* Calculate a hash of files being copied to detect changes for cache invalidation.
* The hash includes file content, metadata (mode, size), and relative paths.
* Note: uid, gid, and mtime are excluded to ensure stable hashes across environments.
*
* @param src Source path pattern for files to copy
* @param dest Destination path where files will be copied
* @param contextPath Base directory for resolving relative paths
* @param ignorePatterns Ignore patterns in `.dockerignore` syntax
* @param resolveSymlinks Whether to resolve symbolic links when hashing
* @param stackTrace Optional stack trace for error reporting
* @returns Hex string hash of all files
* @throws Error if no files match the source pattern
*/
async function calculateFilesHash(src, dest, contextPath, ignorePatterns, resolveSymlinks, stackTrace) {
const srcPath = node_path.default.join(contextPath, src);
const hash = node_crypto.default.createHash("sha256");
const content = `COPY ${src} ${dest}`;
hash.update(content);
const files = await getAllFilesInPath(src, contextPath, ignorePatterns, true);
if (files.length === 0) {
const error = /* @__PURE__ */ new Error(`No files found in ${srcPath}`);
if (stackTrace) error.stack = stackTrace;
throw error;
}
const hashStats = (stats) => {
hash.update(stats.mode.toString());
if (!stats.isDirectory()) hash.update(stats.size.toString());
};
const chunkBuffer = Buffer.allocUnsafe(HASH_CHUNK_SIZE);
for (const file of files) {
const relativePath = file.relativePosix();
hash.update(relativePath);
if (file.isSymbolicLink()) {
const stats = node_fs.default.statSync(file.fullpath(), { throwIfNoEntry: false });
if (!(resolveSymlinks && ((stats === null || stats === void 0 ? void 0 : stats.isFile()) || (stats === null || stats === void 0 ? void 0 : stats.isDirectory())))) {
hashStats(node_fs.default.lstatSync(file.fullpath()));
const content = node_fs.default.readlinkSync(file.fullpath());
hash.update(content);
continue;
}
}
const stats = node_fs.default.statSync(file.fullpath());
hashStats(stats);
if (stats.isFile()) hashFileContent(hash, file.fullpath(), chunkBuffer);
}
return hash.digest("hex");
}
/**
* Convert a stack-trace file name to a filesystem path.
* In ESM modules, stack frames report file:// URLs.
*/
function frameFileToPath(fileName) {
return fileName.startsWith("file:") ? node_url.default.fileURLToPath(fileName) : fileName;
}
/**
* Check whether a stack-trace file name refers to user code, i.e. a file
* outside the SDK's own directory. Node internals (`node:*`) and native
* frames are never user code.
*/
function isUserFile(fileName, sdkDir) {
if (fileName.startsWith("node:") || fileName === "native") return false;
try {
const relative = node_path.default.relative(sdkDir, node_path.default.dirname(frameFileToPath(fileName)));
return relative !== "" && (relative.startsWith("..") || node_path.default.isAbsolute(relative));
} catch (_unused) {
return false;
}
}
/**
* Capture the current stack and locate the first frame in user code.
*
* Frames are selected by boundary rather than by fixed depth: the SDK's own
* directory is derived from the top frame (which is always SDK code — this
* module), and the first frame whose file lies outside it is the user's call
* site. This keeps the result stable when transpilers inject extra frames
* (e.g. TS class-field initializers) or runtimes elide delegating frames
* (e.g. Bun's tail-call elision).
*
* @returns Parsed frames and the index of the user's frame, -1 when no user
* frame is identifiable (e.g. the SDK is bundled into the caller's file)
*/
function captureUserFrames() {
var _frames$;
const frames = parse(/* @__PURE__ */ new Error(), { allowEmpty: true });
const ownFile = (_frames$ = frames[0]) === null || _frames$ === void 0 ? void 0 : _frames$.fileName;
if (!ownFile) return {
frames,
userFrameIndex: -1
};
const sdkDir = node_path.default.dirname(frameFileToPath(ownFile));
return {
frames,
userFrameIndex: frames.findIndex((frame) => frame.fileName !== void 0 && isUserFile(frame.fileName, sdkDir))
};
}
/**
* Get the stack trace starting at the caller's frame in user code.
*
* @returns The stack trace starting at the user's frame, or undefined when no
* user frame is identifiable
*/
function getCallerFrame() {
const { frames, userFrameIndex } = captureUserFrames();
if (userFrameIndex === -1) return;
return frames.slice(userFrameIndex).map((frame) => frame.source).filter((source) => source !== void 0).join("\n");
}
/**
* Get the directory of the caller in user code.
*
* @returns The caller's directory path, or undefined if not available
*/
function getCallerDirectory() {
const { frames, userFrameIndex } = captureUserFrames();
const fileName = userFrameIndex === -1 ? void 0 : frames[userFrameIndex].fileName;
if (!fileName) return;
return node_path.default.dirname(frameFileToPath(fileName));
}
/**
* Convert a numeric file mode to a zero-padded octal string.
*
* @param mode File mode as a number (e.g., 493 for 0o755)
* @returns Zero-padded 4-digit octal string (e.g., "0755")
*
* @example
* ```ts
* padOctal(0o755) // Returns "0755"
* padOctal(0o644) // Returns "0644"
* ```
*/
function padOctal(mode) {
return mode.toString(8).padStart(4, "0");
}
/**
* Create a gzipped tar archive of files matching a pattern, spooled to a
* temporary file on disk.
*
* Spooling instead of buffering keeps memory bounded and gives the archive a
* known size, so the upload can send an exact `Content-Length`. The caller
* owns the archive's lifetime and must invoke `cleanup` once done with it.
* This mirrors the Python SDK's `tar_file_stream`.
*
* @param fileName Glob pattern for files to include
* @param fileContextPath Base directory for resolving file paths
* @param ignorePatterns Ignore patterns to exclude from the archive
* @param resolveSymlinks Whether to follow symbolic links
* @param gzip Whether to gzip the archive
* @returns The archive path, its size in bytes, and a cleanup callback that
* removes the spooled archive. Cleanup is best-effort so it can never mask
* the upload result — a leaked temp dir is non-fatal, the OS reclaims it.
*/
async function spoolTarArchive(fileName, fileContextPath, ignorePatterns, resolveSymlinks, gzip) {
const { create } = await dynamicImport("tar");
const filePaths = (await getAllFilesInPath(fileName, fileContextPath, ignorePatterns, true)).map((file) => file.relativePosix());
const tmpDir = await node_fs.default.promises.mkdtemp(node_path.default.join(node_os.default.tmpdir(), "e2b-template-"));
const tarPath = node_path.default.join(tmpDir, "context.tar.gz");
const cleanup = () => node_fs.default.promises.rm(tmpDir, {
recursive: true,
force: true
}).catch(() => {});
try {
await create({
gzip,
cwd: fileContextPath,
follow: resolveSymlinks,
noDirRecurse: true,
file: tarPath
}, filePaths);
const { size } = await node_fs.default.promises.stat(tarPath);
return {
path: tarPath,
size,
cleanup
};
} catch (err) {
await cleanup();
throw err;
}
}
/**
* Get the array index for a build step based on its name.
*
* Special steps:
* - BASE_STEP_NAME: Returns 0 (first step)
* - FINALIZE_STEP_NAME: Returns the last index
* - Numeric strings: Converted to number
*
* @param step Build step name or number as string
* @param stackTracesLength Total number of stack traces (used for FINALIZE_STEP_NAME)
* @returns Index for the build step
*/
function getBuildStepIndex(step, stackTracesLength) {
if (step === "base") return 0;
if (step === "finalize") return stackTracesLength - 1;
return Number(step);
}
/**
* Read GCP service account JSON from a file or object.
*
* @param contextPath Base directory for resolving relative file paths
* @param pathOrContent Either a path to a JSON file or a service account object
* @returns Service account JSON as a string
*/
function readGCPServiceAccountJSON(contextPath, pathOrContent) {
if (typeof pathOrContent === "string") return node_fs.default.readFileSync(node_path.default.join(contextPath, pathOrContent), "utf-8");
return JSON.stringify(pathOrContent);
}
//#endregion
//#region src/template/buildApi.ts
async function requestBuild(client, { name, tags, cpuCount, maxCpuCount, memoryMB, minFreeDiskMb }, signal) {
const requestBuildRes = await client.api.POST("/v3/templates", {
body: {
name,
tags,
cpuCount,
maxCpuCount,
memoryMB,
minFreeDiskMb
},
signal
});
const error = handleApiError(requestBuildRes, BuildError);
if (error) throw error;
if (!requestBuildRes.data) throw new BuildError("Failed to request build");
return requestBuildRes.data;
}
async function getFileUploadLink(client, { templateID, filesHash }, stackTrace, signal) {
const fileUploadLinkRes = await client.api.GET("/templates/{templateID}/files/{hash}", {
params: { path: {
templateID,
hash: filesHash
} },
signal
});
const error = handleApiError(fileUploadLinkRes, FileUploadError, stackTrace);
if (error) throw error;
if (!fileUploadLinkRes.data) throw new FileUploadError("Failed to get file upload link", stackTrace);
return fileUploadLinkRes.data;
}
async function uploadFile(options, stackTrace, abortOpts) {
const { fileName, url, headers, fileContextPath, ignorePatterns, resolveSymlinks, gzip } = options;
let cleanup;
try {
var _abortOpts$requestTim;
const tar = await spoolTarArchive(fileName, fileContextPath, ignorePatterns, resolveSymlinks, gzip);
cleanup = tar.cleanup;
const signal = buildRequestSignal((_abortOpts$requestTim = abortOpts === null || abortOpts === void 0 ? void 0 : abortOpts.requestTimeoutMs) !== null && _abortOpts$requestTim !== void 0 ? _abortOpts$requestTim : FILE_UPLOAD_TIMEOUT_MS, abortOpts === null || abortOpts === void 0 ? void 0 : abortOpts.signal);
const res = await putFileStream(url, tar.path, tar.size, signal, headers);
if (!res.ok) throw new FileUploadError(`Failed to upload file: ${res.statusText}`, stackTrace);
} catch (error) {
if (error instanceof FileUploadError) throw error;
throw new FileUploadError(`Failed to upload file: ${error}`, stackTrace);
} finally {
await (cleanup === null || cleanup === void 0 ? void 0 : cleanup());
}
}
async function putFileStream(url, filePath, size, signal, headers) {
var _ref;
const undici = await loadUndici();
return await ((_ref = undici === null || undici === void 0 ? void 0 : undici.fetch) !== null && _ref !== void 0 ? _ref : fetch)(url, {
method: "PUT",
body: node_stream.default.Readable.toWeb(node_fs.default.createReadStream(filePath)),
headers: _objectSpread2(_objectSpread2({}, Object.fromEntries(Object.entries(headers !== null && headers !== void 0 ? headers : {}).filter(([name]) => name.toLowerCase() !== "content-length"))), {}, { "Content-Length": size.toString() }),
duplex: "half",
signal
});
}
async function triggerBuild(client, { templateID, buildID, template }, signal) {
const error = handleApiError(await client.api.POST("/v2/templates/{templateID}/builds/{buildID}", {
params: { path: {
templateID,
buildID
} },
body: template,
signal
}), BuildError);
if (error) throw error;
}
function mapLogEntry(entry) {
return new LogEntry(new Date(entry.timestamp), entry.level, entry.message);
}
function mapBuildStatusReason(reason) {
var _reason$logEntries;
if (!reason) return;
return {
message: reason.message,
step: reason.step,
logEntries: ((_reason$logEntries = reason.logEntries) !== null && _reason$logEntries !== void 0 ? _reason$logEntries : []).map(mapLogEntry)
};
}
async function getBuildStatus(client, { templateID, buildID, logsOffset }, signal) {
const buildStatusRes = await client.api.GET("/templates/{templateID}/builds/{buildID}/status", {
params: {
path: {
templateID,
buildID
},
query: { logsOffset }
},
signal
});
const error = handleApiError(buildStatusRes, BuildError);
if (error) throw error;
if (!buildStatusRes.data) throw new BuildError("Failed to get build status");
return {
buildID: buildStatusRes.data.buildID,
templateID: buildStatusRes.data.templateID,
status: buildStatusRes.data.status,
logEntries: buildStatusRes.data.logEntries.map(mapLogEntry),
logs: buildStatusRes.data.logs,
reason: mapBuildStatusReason(buildStatusRes.data.reason)
};
}
async function checkAliasExists(client, { alias }, signal) {
const aliasRes = await client.api.GET("/templates/aliases/{alias}", {
params: { path: { alias } },
signal
});
if (aliasRes.response.status === 404) return false;
if (aliasRes.response.status === 403) return true;
const error = handleApiError(aliasRes, TemplateError);
if (error) throw error;
return aliasRes.data !== void 0;
}
async function waitForBuildFinish(client, { templateID, buildID, onBuildLogs, logsRefreshFrequency, stackTraces, signal, requestTimeoutMs }) {
let logsOffset = 0;
let status = "building";
const pollStatus = async () => {
const buildStatus = await getBuildStatus(client, {
templateID,
buildID,
logsOffset
}, buildRequestSignal(requestTimeoutMs, signal));
logsOffset += buildStatus.logEntries.length;
buildStatus.logEntries.forEach((logEntry) => onBuildLogs === null || onBuildLogs === void 0 ? void 0 : onBuildLogs(logEntry));
return buildStatus;
};
while (status === "building" || status === "waiting") {
signal === null || signal === void 0 || signal.throwIfAborted();
const buildStatus = await pollStatus();
status = buildStatus.status;
switch (status) {
case "ready":
case "error": {
var _buildStatus$reason, _buildStatus$reason$m, _buildStatus$reason2;
let tailStatus = buildStatus;
while (tailStatus.logEntries.length > 0) {
signal === null || signal === void 0 || signal.throwIfAborted();
tailStatus = await pollStatus();
}
if (status === "ready") return;
let stackError;
if (((_buildStatus$reason = buildStatus.reason) === null || _buildStatus$reason === void 0 ? void 0 : _buildStatus$reason.step) !== void 0) stackError = stackTraces[getBuildStepIndex(buildStatus.reason.step, stackTraces.length)];
throw new BuildError((_buildStatus$reason$m = buildStatus === null || buildStatus === void 0 || (_buildStatus$reason2 = buildStatus.reason) === null || _buildStatus$reason2 === void 0 ? void 0 : _buildStatus$reason2.message) !== null && _buildStatus$reason$m !== void 0 ? _buildStatus$reason$m : "Unknown error", stackError);
}
}
await new Promise((resolve) => setTimeout(resolve, logsRefreshFrequency));
}
throw new BuildError("Unknown build error occurred.");
}
async function assignTags(client, { targetName, tags }, signal) {
const res = await client.api.POST("/templates/tags", {
body: {
target: targetName,
tags
},
signal
});
const error = handleApiError(res, TemplateError);
if (error) throw error;
if (!res.data) throw new TemplateError("Failed to assign tags");
return {
buildId: res.data.buildID,
tags: res.data.tags
};
}
async function removeTags(client, { name, tags }, signal) {
const error = handleApiError(await client.api.DELETE("/templates/tags", {
body: {
name,
tags
},
signal
}), TemplateError);
if (error) throw error;
}
async function getTemplateTags(client, { templateID }, signal) {
const res = await client.api.GET("/templates/{templateID}/tags", {
params: { path: { templateID } },
signal
});
const error = handleApiError(res, TemplateError);
if (error) throw error;
if (!res.data) throw new TemplateError("Failed to get template tags");
return res.data.map((item) => ({
tag: item.tag,
buildId: item.buildID,
createdAt: new Date(item.createdAt)
}));
}
async function deleteTemplate(client, { templateID }, signal) {
const res = await client.api.DELETE("/templates/{templateID}", {
params: { path: { templateID } },
signal
});
if (res.response.status === 404) return false;
const error = handleApiError(res, TemplateError);
if (error) throw error;
return true;
}
//#endregion
//#region src/sandbox/sandboxApi.ts
const SNAPSHOT_MODES = ["full", "filesystem"];
/**
* Resolve `mode` (or the deprecated `keepMemory`) into the API's `memory`
* flag. Unset, it stays `undefined` so the API default (currently a full
* snapshot) applies. `field` names the option in error messages.
*/
function snapshotMemory(field, mode, keepMemory) {
if (mode != null && keepMemory != null) throw new InvalidArgumentError(`Pass either ${field} or the deprecated keepMemory, not both.`);
if (mode == null) return keepMemory !== null && keepMemory !== void 0 ? keepMemory : void 0;
if (!SNAPSHOT_MODES.includes(mode)) throw new InvalidArgumentError(`${field} must be one of: ${SNAPSHOT_MODES.join(", ")} (got ${JSON.stringify(mode)}).`);
return mode === "full";
}
/**
* Result of one requested fork as returned by the API — either the raw
* connection info of the created sandbox, or the error that prevented it
* from starting. Per-fork error codes map to the same error classes as other
* API errors (e.g. 429 to `RateLimitError`).
*/
const MAX_FORK_COUNT = 20;
function validateForkCount(count) {
if (count === void 0) return;
if (!Number.isInteger(count) || count < 1 || count > MAX_FORK_COUNT) throw new InvalidArgumentError(`count must be an integer between 1 and ${MAX_FORK_COUNT}`);
}
function toSandboxCreateResponse(sandbox) {
return {
sandboxId: sandbox.sandboxID,
sandboxDomain: sandbox.domain || void 0,
envdVersion: sandbox.envdVersion,
envdAccessToken: sandbox.envdAccessToken,
trafficAccessToken: sandbox.trafficAccessToken || void 0
};
}
function resolveNetworkSelector(selector, rules) {
if (selector === void 0) return;
if (typeof selector === "function") return selector({
allTraffic: ALL_TRAFFIC,
rules
});
return selector;
}
/**
* Build the context handed to `transform` callbacks. See
* {@link iamTokenPlaceholders} for what `validate` controls.
*/
function buildTransformContext(tokenNames, { validate }) {
return { iam: { tokens: iamTokenPlaceholders(tokenNames, { validate }) } };
}
function isPlainObject(value) {
if (typeof value !== "object" || value === null) return false;
const proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === null;
}
/** Name the shape a `transform` callback returned, for the error message. */
function describeValue(value) {
var _value$constructor$na, _value$constructor;
if (value === null) return "null";
if (typeof value !== "object") return typeof value;
return Array.isArray(value) ? "array" : (_value$constructor$na = (_value$constructor = value.constructor) === null || _value$constructor === void 0 ? void 0 : _value$constructor.name) !== null && _value$constructor$na !== void 0 ? _value$constructor$na : "object";
}
function resolveRulesForBody(rules, ctx) {
const out = {};
for (const [host, hostRules] of rules) out[host] = hostRules.map((rule) => {
if (rule.transform == null) return {};
if (typeof rule.transform !== "function") return { transform: rule.transform };
const transform = rule.transform(ctx);
if (typeof (transform === null || transform === void 0 ? void 0 : transform.then) === "function") {
Promise.resolve(transform).catch(() => {});
throw new InvalidArgumentError(`Network transform callback for '${host}' must be synchronous, it returned a promise. Resolve the value before creating the sandbox.`);
}
if (!isPlainObject(transform)) throw new InvalidArgumentError(`Network transform callback for '${host}' must return a transform object, got ${describeValue(transform)}.`);
return { transform };
});
return out;
}
/**
* Rebuild the proxy config from the known fields so stray properties on the
* caller's object never reach the wire and a later mutation of it cannot alter
* the in-flight request. Address reachability is the server's — it is the only
* side that can tell whether the address resolves, and to where. The required
* `address` is checked here so an untyped caller that omits it is told which
* option is wrong instead of getting an API error about a body it never wrote.
*/
function buildEgressProxyBody(egressProxy) {
if (!isPlainObject(egressProxy) || typeof egressProxy.address !== "string") throw new InvalidArgumentError(`network egressProxy must be an object with a string 'address' (e.g. 'proxy.example.com:1080').`);
const tls = egressProxy.tls;
if (tls != null && typeof tls !== "boolean" && !isPlainObject(tls)) throw new InvalidArgumentError("network egressProxy tls must be a boolean or an object.");
if (isPlainObject(tls) && Object.prototype.hasOwnProperty.call(tls, "enabled")) throw new InvalidArgumentError("network egressProxy tls takes no 'enabled'; pass tls: true or tls: false instead.");
const tlsBody = tls === true ? { enabled: true } : tls != null && typeof tls === "object" ? _objectSpread2(_objectSpread2({ enabled: true }, tls.serverName != null ? { serverName: tls.serverName } : {}), tls.caCert != null ? { caCert: tls.caCert } : {}) : void 0;
return _objectSpread2(_objectSpread2(_objectSpread2({ address: egressProxy.address }, egressProxy.username != null ? { username: egressProxy.username } : {}), egressProxy.password != null ? { password: egressProxy.password } : {}), tlsBody !== void 0 ? { tls: tlsBody } : {});
}
function buildNetworkEgress(network, transformContext) {
var _network$rules;
const rules = network.rules instanceof Map ? network.rules : new Map(Object.entries((_network$rules = network.rules) !== null && _network$rules !== void 0 ? _network$rules : {}));
const allowOut = resolveNetworkSelector(network.allowOut, rules);
const denyOut = resolveNetworkSelector(network.denyOut, rules);
return _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, allowOut !== void 0 ? { allowOut } : {}), denyOut !== void 0 ? { denyOut } : {}), network.egressProxy != null ? { egressProxy: buildEgressProxyBody(network.egressProxy) } : {}), network.dnsServers != null ? { dnsServers: [...network.dnsServers] } : {}), network.rules !== void 0 ? { rules: resolveRulesForBody(rules, transformContext) } : {});
}
/**
* Map the wire proxy config into the SDK-owned shape: `password` is dropped
* because the API never returns it, and the wire's `null` for "no proxy" is
* normalized so the union never reaches a consumer.
*/
function fromApiEgressProxy(egressProxy) {
var _egressProxy$tls;
if (!egressProxy) return;
return _objectSpread2(_objectSpread2({ address: egressProxy.address }, egressProxy.username !== void 0 ? { username: egressProxy.username } : {}), ((_egressProxy$tls = egressProxy.tls) === null || _egressProxy$tls === void 0 ? void 0 : _egressProxy$tls.enabled) === true ? { tls: egressProxy.tls.serverName !== void 0 ? { serverName: egressProxy.tls.serverName } : {} } : {});
}
function buildNetworkBody(network, iam) {
var _iam$tokens;
if (!network) return;
return _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, buildNetworkEgress(network, buildTransformContext(Object.keys((_iam$tokens = iam === null || iam === void 0 ? void 0 : iam.tokens) !== null && _iam$tokens !== void 0 ? _iam$tokens : {}), { validate: true }))), network.allowPublicTraffic !== void 0 ? { allowPublicTraffic: network.allowPublicTraffic } : {}), network.maskRequestHost !== void 0 ? { maskRequestHost: network.maskRequestHost } : {}), network.httpsPorts !== void 0 ? { httpsPorts: network.httpsPorts } : {});
}
function buildIamBody(iam) {
var _iam$tokens2;
const tokens = {};
for (const [name, token] of Object.entries((_iam$tokens2 = iam === null || iam === void 0 ? void 0 : iam.tokens) !== null && _iam$tokens2 !== void 0 ? _iam$tokens2 : {})) {
if (!token) continue;
if (typeof token.audience !== "string" || typeof token.tokenType !== "string") throw new InvalidArgumentError(`iam token '${name}' must have string 'audience' and 'tokenType' properties.`);
validateIamTokenName(name);
tokens[name] = {
audience: token.audience,
tokenType: token.tokenType
};
}
if (Object.keys(tokens).length === 0) return;
return { tokens };
}
function buildNetworkUpdateBody(network) {
return _objectSpread2(_objectSpread2({}, buildNetworkEgress(network, buildTransformContext([], { validate: false }))), network.allowInternetAccess !== void 0 ? { allow_internet_access: network.allowInternetAccess } : {});
}
var SandboxApi = class extends ClientFactory {
constructor() {
super();
}
/**
* Kill the sandbox specified by sandbox ID.
*
* @param sandboxId sandbox ID.
* @param opts connection options.
*
* @returns `true` if the sandbox was found and killed, `false` otherwise.
*/
static async kill(sandboxId, opts) {
var _this = this;
var _res$error;
const apiOpts = _this.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
if (config.debug) return true;
const res = await new ApiClient(config).api.DELETE("/sandboxes/{sandboxID}", {
params: { path: { sandboxID: sandboxId } },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (((_res$error = res.error) === null || _res$error === void 0 ? void 0 : _res$error.code) === 404) return false;
const err = handleApiError(res);
if (err) throw err;
return true;
}
/**
* Get sandbox information like sandbox ID, template, metadata, started at/end at date.
*
* @param sandboxId sandbox ID.
* @param opts connection options.
*
* @returns sandbox information.
*/
static async getInfo(sandboxId, opts) {
var _this2 = this;
var _res$error2, _res$data$metadata, _res$data$allowIntern, _res$data$network$rul, _res$data$network$all, _res$data$volumeMount;
const apiOpts = _this2.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.GET("/sandboxes/{sandboxID}", {
params: { path: { sandboxID: sandboxId } },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (((_res$error2 = res.error) === null || _res$error2 === void 0 ? void 0 : _res$error2.code) === 404) throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
const err = handleApiError(res);
if (err) throw err;
if (!res.data) throw new Error("Sandbox not found");
return _objectSpread2(_objectSpread2({
sandboxId: res.data.sandboxID,
templateId: res.data.templateID
}, res.data.alias && { name: res.data.alias }), {}, {
metadata: (_res$data$metadata = res.data.metadata) !== null && _res$data$metadata !== void 0 ? _res$data$metadata : {},
allowInternetAccess: (_res$data$allowIntern = res.data.allowInternetAccess) !== null && _res$data$allowIntern !== void 0 ? _res$data$allowIntern : void 0,
envdVersion: res.data.envdVersion,
startedAt: new Date(res.data.startedAt),
endAt: new Date(res.data.endAt),
state: res.data.state,
cpuCount: res.data.cpuCount,
memoryMB: res.data.memoryMB,
network: res.data.network ? _objectSpread2(_objectSpread2({
allowOut: res.data.network.allowOut,
denyOut: res.data.network.denyOut,
rules: (_res$data$network$rul = res.data.network.rules) !== null && _res$data$network$rul !== void 0 ? _res$data$network$rul : void 0,
egressProxy: fromApiEgressProxy(res.data.network.egressProxy)
}, res.data.network.dnsServers !== void 0 ? { dnsServers: res.data.network.dnsServers } : {}), {}, {
allowPublicTraffic: (_res$data$network$all = res.data.network.allowPublicTraffic) !== null && _res$data$network$all !== void 0 ? _res$data$network$all : void 0,
maskRequestHost: res.data.network.maskRequestHost,
httpsPorts: res.data.network.httpsPorts
}) : void 0,
lifecycle: res.data.lifecycle ? {
onTimeout: res.data.lifecycle.onTimeout,
autoResume: res.data.lifecycle.autoResume
} : void 0,
sandboxDomain: res.data.domain || void 0,
volumeMounts: (_res$data$volumeMount = res.data.volumeMounts) !== null && _res$data$volumeMount !== void 0 ? _res$data$volumeMount : [],
envdUnresponsiveAtPause: res.data.envdUnresponsiveAtPause || void 0
});
}
/**
* @deprecated Use {@link Sandbox.getInfo} instead.
*
* @param sandboxId sandbox ID.
* @param opts connection options.
*
* @returns sandbox information.
*/
static async getFullInfo(sandboxId, opts) {
return await this.getInfo(sandboxId, opts);
}
/**
* Get the metrics of the sandbox.
*
* @param sandboxId sandbox ID.
* @param opts sandbox metrics options.
*
* @returns List of sandbox metrics containing CPU, memory and disk usage information.
*/
static async getMetrics(sandboxId, opts) {
var _this4 = this;
var _res$error3, _res$data$map, _res$data;
const apiOpts = _this4.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
if (config.debug) return [];
const client = new ApiClient(config);
const start = (apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.start) ? Math.round(apiOpts.start.getTime() / 1e3) : void 0;
const end = (apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.end) ? Math.round(apiOpts.end.getTime() / 1e3) : void 0;
const res = await client.api.GET("/sandboxes/{sandboxID}/metrics", {
params: {
path: { sandboxID: sandboxId },
query: {
start,
end
}
},
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (((_res$error3 = res.error) === null || _res$error3 === void 0 ? void 0 : _res$error3.code) === 404) throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
const err = handleApiError(res);
if (err) throw err;
return (_res$data$map = (_res$data = res.data) === null || _res$data === void 0 ? void 0 : _res$data.map((metric) => ({
timestamp: new Date(metric.timestamp),
cpuUsedPct: metric.cpuUsedPct,
cpuCount: metric.cpuCount,
memUsed: metric.memUsed,
memTotal: metric.memTotal,
memCache: metric.memCache,
diskUsed: metric.diskUsed,
diskTotal: metric.diskTotal
}))) !== null && _res$data$map !== void 0 ? _res$data$map : [];
}
/**
* Set the timeout of the specified sandbox.
* After the timeout expires the sandbox will be automatically killed.
*
* This method can extend or reduce the sandbox timeout set when creating the sandbox or from the last call to {@link Sandbox.setTimeout}.
*
* Maximum time a sandbox can be kept alive is 24 hours (86_400_000 milliseconds) for Pro users and 1 hour (3_600_000 milliseconds) for Hobby users.
*
* @param sandboxId sandbox ID.
* @param timeoutMs timeout in **milliseconds**.
* @param opts connection options.
*/
static async setTimeout(sandboxId, timeoutMs, opts) {
var _this5 = this;
var _res$error4;
const apiOpts = _this5.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.POST("/sandboxes/{sandboxID}/timeout", {
params: { path: { sandboxID: sandboxId } },
body: { timeout: timeoutToSeconds(timeoutMs) },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (((_res$error4 = res.error) === null || _res$error4 === void 0 ? void 0 : _res$error4.code) === 404) throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
const err = handleApiError(res);
if (err) throw err;
}
/**
* Update the network configuration of a running sandbox.
*
* Replaces the current egress configuration atomically — fields that are
* omitted are cleared on the server.
*
* @param sandboxId sandbox ID.
* @param network new network configuration.
* @param opts connection options.
*/
static async updateNetwork(sandboxId, network, opts) {
var _this6 = this;
var _res$error5;
const apiOpts = _this6.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.PUT("/sandboxes/{sandboxID}/network", {
params: { path: { sandboxID: sandboxId } },
body: buildNetworkUpdateBody(network),
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (((_res$error5 = res.error) === null || _res$error5 === void 0 ? void 0 : _res$error5.code) === 404) throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
const err = handleApiError(res);
if (err) throw err;
}
/**
* Pause the sandbox specified by sandbox ID.
*
* @param sandboxId sandbox ID.
* @param opts pause options, including `mode` and connection options.
*
* @returns `true` if the sandbox got paused, `false` if the sandbox was already paused.
*/
static async pause(sandboxId, opts) {
var _this7 = this;
var _res$error6, _res$error7;
const apiOpts = _this7.resolveOpts(opts);
const memory = snapshotMemory("mode", apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.mode, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.keepMemory);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.POST("/sandboxes/{sandboxID}/pause", {
params: { path: { sandboxID: sandboxId } },
body: {
memory,
requireHealthyGuest: (apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.strict) ? true : void 0
},
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (((_res$error6 = res.error) === null || _res$error6 === void 0 ? void 0 : _res$error6.code) === 404) throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
if (((_res$error7 = res.error) === null || _res$error7 === void 0 ? void 0 : _res$error7.code) === 409) return false;
const err = handleApiError(res);
if (err) throw err;
return true;
}
/**
* @deprecated Use {@link SandboxApi.pause} instead.
*/
static async betaPause(sandboxId, opts) {
return this.pause(sandboxId, opts);
}
/**
* Create a snapshot from a sandbox.
*
* The sandbox will be paused while the snapshot is being created.
* The snapshot can be used to create new sandboxes with the same state.
* The snapshot is a persistent image that survives sandbox deletion.
*
* @param sandboxId sandbox ID to create snapshot from.
* @param opts snapshot creation options including optional name, `mode`
* (`'filesystem'` takes a filesystem-only snapshot), and connection options.
*
* @throws {@link SandboxError} with `statusCode` 400 when `mode: 'filesystem'`
* is requested but the feature is not enabled for the team
* (`snapshot_filesystem_only_disabled`), and with `statusCode` 409 when the
* sandbox's node runs an orchestrator that predates the option
* (`snapshot_filesystem_only_unsupported_node`). A full memory snapshot
* still works in both cases; for 409, pause and resume the sandbox and retry.
*
* @returns snapshot information including the snapshot name that can be used with Sandbox.create().
*/
static async createSnapshot(sandboxId, opts) {
var _this9 = this;
var _res$error8, _names;
const apiOpts = _this9.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.POST("/sandboxes/{sandboxID}/snapshots", {
params: { path: { sandboxID: sandboxId } },
body: _objectSpread2(_objectSpread2({}, (apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.name) ? { name: apiOpts.name } : {}), {}, { memory: snapshotMemory("mode", apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.mode) }),
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (((_res$error8 = res.error) === null || _res$error8 === void 0 ? void 0 : _res$error8.code) === 404) throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
const err = handleApiError(res);
if (err) throw err;
return {
snapshotId: res.data.snapshotID,
names: (_names = res.data.names) !== null && _names !== void 0 ? _names : []
};
}
/**
* List all snapshots.
*
* @param opts list options including filters and pagination.
*
* @returns paginator for listing snapshots.
*/
static listSnapshots(opts) {
return new SnapshotPaginator(this.resolveOpts(opts));
}
/**
* Delete a snapshot.
*
* @param snapshotId snapshot ID.
* @param opts connection options.
*
* @returns `true` if the snapshot was deleted, `false` if it was not found.
*/
static async deleteSnapshot(snapshotId, opts) {
const apiOpts = this.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
return deleteTemplate(new ApiClient(config), { templateID: snapshotId }, config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal));
}
static async createSandbox(template, timeoutMs, opts) {
var _this11 = this;
var _opts$lifecycle, _opts$lifecycle$autoR, _opts$lifecycle2;
const idempotencyKey = resolveIdempotencyKey(opts === null || opts === void 0 ? void 0 : opts.idempotencyKey);
const requestedOnTimeout = opts === null || opts === void 0 || (_opts$lifecycle = opts.lifecycle) === null || _opts$lifecycle === void 0 ? void 0 : _opts$lifecycle.onTimeout;
const onTimeoutConfigured = requestedOnTimeout != null;
const onTimeout = requestedOnTimeout !== null && requestedOnTimeout !== void 0 ? requestedOnTimeout : "kill";
const action = typeof onTimeout === "string" ? onTimeout : onTimeout.action;
const allowedActions = ["pause", "kill"];
if (onTimeoutConfigured && !allowedActions.includes(action)) throw new InvalidArgumentError(`${typeof onTimeout === "string" ? "onTimeout" : "onTimeout.action"} must be one of: ${allowedActions.join(", ")} (got ${JSON.stringify(action)}).`);
const pauseOpts = typeof onTimeout === "string" ? {} : onTimeout;
const hasMode = "mode" in pauseOpts || "keepMemory" in pauseOpts;
const autoResume = (_opts$lifecycle$autoR = opts === null || opts === void 0 || (_opts$lifecycle2 = opts.lifecycle) === null || _opts$lifecycle2 === void 0 ? void 0 : _opts$lifecycle2.autoResume) !== null && _opts$lifecycle$autoR !== void 0 ? _opts$lifecycle$autoR : void 0;
if (hasMode && action !== "pause") throw new InvalidArgumentError("onTimeout.mode is only allowed when action is 'pause'.");
const memory = snapshotMemory("onTimeout.mode", pauseOpts.mode, pauseOpts.keepMemory);
if (autoResume && action !== "pause") throw new InvalidArgumentError("autoResume can only be true when onTimeout action is 'pause'.");
if (memory === false && autoResume) throw new InvalidArgumentError("autoResume: true is not a valid value with mode: 'filesystem' - a filesystem-only snapshot cannot be auto-resumed by traffic and must be resumed explicitly using Sandbox.connect().");
const iam = buildIamBody(opts === null || opts === void 0 ? void 0 : opts.iam);
const body = {
templateID: template,
metadata: opts === null || opts === void 0 ? void 0 : opts.metadata,
mcp: opts === null || opts === void 0 ? void 0 : opts.mcp,
envVars: opts === null || opts === void 0 ? void 0 : opts.envs,
timeout: timeoutMs === void 0 ? void 0 : timeoutToSeconds(timeoutMs),
cpuCount: opts === null || opts === void 0 ? void 0 : opts.cpuCount,
allow_internet_access: opts === null || opts === void 0 ? void 0 : opts.allowInternetAccess,
network: buildNetworkBody(opts === null || opts === void 0 ? void 0 : opts.network, iam),
iam,
autoPause: onTimeoutConfigured ? action === "pause" : void 0,
autoPauseMemory: action === "pause" ? memory : void 0,
autoResume: autoResume === void 0 ? void 0 : { enabled: autoResume }
};
if (opts === null || opts === void 0 ? void 0 : opts.volumeMounts) body.volumeMounts = Object.entries(opts.volumeMounts).map(([mountPath, vol]) => ({
name: typeof vol === "string" ? vol : vol.name,
path: mountPath
}));
const apiOpts = _this11.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.POST("/v2/sandboxes", {
body,
params: idempotencyKey !== void 0 ? { header: { "Idempotency-Key": idempotencyKey } } : void 0,
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
const err = handleApiError(res);
if (err) throw err;
if ((0, compare_versions.compareVersions)(res.data.envdVersion, "0.1.0") < 0) {
await _this11.kill(res.data.sandboxID, apiOpts);
throw new TemplateError("You need to update the template to use the new SDK.");
}
return toSandboxCreateResponse(res.data);
}
static async forkSandbox(sandboxId, timeoutMs, count, opts) {
var _this12 = this;
var _res$data2;
validateForkCount(count);
const apiOpts = _this12.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.POST("/sandboxes/{sandboxID}/fork", {
params: { path: { sandboxID: sandboxId } },
body: {
timeout: timeoutMs === void 0 ? void 0 : timeoutToSeconds(timeoutMs),
count
},
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (res.response.status === 404) {
var _res$error$message, _res$error9;
throw new SandboxNotFoundError((_res$error$message = (_res$error9 = res.error) === null || _res$error9 === void 0 ? void 0 : _res$error9.message) !== null && _res$error$message !== void 0 ? _res$error$message : `Sandbox ${sandboxId} not found`);
}
const err = handleApiError(res);
if (err) throw err;
return ((_res$data2 = res.data) !== null && _res$data2 !== void 0 ? _res$data2 : []).map((result) => {
if (result.error || !result.sandbox) {
if (!result.error) return new SandboxError("Failed to start forked sandbox");
if (result.error.code === 404) return new NotFoundError(`${result.error.code}: ${result.error.message}`);
return apiErrorFromCode(result.error.code, result.error.message);
}
return toSandboxCreateResponse(result.sandbox);
});
}
static async connectSandbox(sandboxId, opts) {
var _this13 = this;
var _apiOpts$onResume, _res$error10;
const apiOpts = _this13.resolveOpts(opts);
const timeoutMs = apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.timeoutMs;
const onResume = (_apiOpts$onResume = apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.onResume) !== null && _apiOpts$onResume !== void 0 ? _apiOpts$onResume : void 0;
const allowedOnResume = ["restore", "reboot"];
if (onResume !== void 0 && !allowedOnResume.includes(onResume)) throw new InvalidArgumentError(`onResume must be one of: ${allowedOnResume.join(", ")} (got ${JSON.stringify(onResume)}).`);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.POST("/v2/sandboxes/{sandboxID}/connect", {
params: { path: { sandboxID: sandboxId } },
body: {
timeout: timeoutMs === void 0 ? void 0 : timeoutToSeconds(timeoutMs),
memory: onResume === "reboot" ? false : void 0
},
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
if (((_res$error10 = res.error) === null || _res$error10 === void 0 ? void 0 : _res$error10.code) === 404) throw new SandboxNotFoundError(`Paused sandbox ${sandboxId} not found`);
const err = handleApiError(res);
if (err) throw err;
return toSandboxCreateResponse(res.data);
}
};
/**
* Paginator for listing sandboxes.
*
* @example
* ```ts
* const paginator = Sandbox.list()
* while (paginator.hasNext) {
* const sandboxes = await paginator.nextItems()
* console.log(sandboxes)
* }
* ```
*/
var SandboxPaginator = class extends Paginator {
constructor(opts) {
super(opts, opts === null || opts === void 0 ? void 0 : opts.limit, opts === null || opts === void 0 ? void 0 : opts.nextToken);
_defineProperty(this, "query", void 0);
_defineProperty(this, "order", void 0);
this.query = opts === null || opts === void 0 ? void 0 : opts.query;
this.order = opts === null || opts === void 0 ? void 0 : opts.order;
}
async nextItems(opts) {
var _this14 = this;
var _this$query, _this$query2, _this$query3, _this$query4, _res$data3;
if (!_this14.hasNext) throw new Error("No more items to fetch");
let metadata = void 0;
if ((_this$query = _this14.query) === null || _this$query === void 0 ? void 0 : _this$query.metadata) {
const encodedPairs = Object.fromEntries(Object.entries(_this14.query.metadata).map(([key, value]) => [encodeURIComponent(key), encodeURIComponent(value)]));
metadata = new URLSearchParams(encodedPairs).toString();
}
const apiOpts = ConnectionConfig.mergeOpts(_this14.opts, opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.GET("/v2/sandboxes", {
params: { query: {
metadata,
state: (_this$query2 = _this14.query) === null || _this$query2 === void 0 ? void 0 : _this$query2.state,
startedAfter: (_this$query3 = _this14.query) === null || _this$query3 === void 0 || (_this$query3 = _this$query3.startedAfter) === null || _this$query3 === void 0 ? void 0 : _this$query3.toISOString(),
template: ((_this$query4 = _this14.query) === null || _this$query4 === void 0 ? void 0 : _this$query4.template) || void 0,
order: _this14.order,
limit: _this14.limit,
nextToken: _this14.nextToken
} },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
const err = handleApiError(res);
if (err) throw err;
_this14.updatePagination(res.response);
return ((_res$data3 = res.data) !== null && _res$data3 !== void 0 ? _res$data3 : []).map((sandbox) => {
var _sandbox$metadata, _sandbox$volumeMounts;
return _objectSpread2(_objectSpread2({
sandboxId: sandbox.sandboxID,
templateId: sandbox.templateID
}, sandbox.alias && { name: sandbox.alias }), {}, {
metadata: (_sandbox$metadata = sandbox.metadata) !== null && _sandbox$metadata !== void 0 ? _sandbox$metadata : {},
startedAt: new Date(sandbox.startedAt),
endAt: new Date(sandbox.endAt),
state: sandbox.state,
cpuCount: sandbox.cpuCount,
memoryMB: sandbox.memoryMB,
envdVersion: sandbox.envdVersion,
volumeMounts: (_sandbox$volumeMounts = sandbox.volumeMounts) !== null && _sandbox$volumeMounts !== void 0 ? _sandbox$volumeMounts : [],
envdUnresponsiveAtPause: sandbox.envdUnresponsiveAtPause || void 0
});
});
}
};
/**
* Paginator for listing snapshots.
*
* @example
* ```ts
* const paginator = Sandbox.listSnapshots()
* while (paginator.hasNext) {
* const snapshots = await paginator.nextItems()
* console.log(snapshots)
* }
* ```
*/
var SnapshotPaginator = class extends Paginator {
constructor(opts) {
super(opts, opts === null || opts === void 0 ? void 0 : opts.limit, opts === null || opts === void 0 ? void 0 : opts.nextToken);
_defineProperty(this, "sandboxId", void 0);
_defineProperty(this, "name", void 0);
this.sandboxId = opts === null || opts === void 0 ? void 0 : opts.sandboxId;
this.name = opts === null || opts === void 0 ? void 0 : opts.name;
}
async nextItems(opts) {
var _this15 = this;
var _res$data4;
if (!_this15.hasNext) throw new Error("No more items to fetch");
const apiOpts = ConnectionConfig.mergeOpts(_this15.opts, opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.GET("/snapshots", {
params: { query: {
sandboxID: _this15.sandboxId,
name: _this15.name,
limit: _this15.limit,
nextToken: _this15.nextToken
} },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
const err = handleApiError(res);
if (err) throw err;
_this15.updatePagination(res.response);
return ((_res$data4 = res.data) !== null && _res$data4 !== void 0 ? _res$data4 : []).map((snapshot) => {
var _snapshot$names;
return {
snapshotId: snapshot.snapshotID,
names: (_snapshot$names = snapshot.names) !== null && _snapshot$names !== void 0 ? _snapshot$names : []
};
});
}
};
//#endregion
//#region src/sandbox/index.ts
/**
* E2B cloud sandbox is a secure and isolated cloud environment.
*
* The sandbox allows you to:
* - Access Linux OS
* - Create, list, and delete files and directories
* - Run commands
* - Run isolated code
* - Access the internet
*
* Check docs [here](https://e2b.dev/docs).
*
* Use {@link Sandbox.create} to create a new sandbox.
*
* @example
* ```ts
* import { Sandbox } from 'e2b'
*
* const sandbox = await Sandbox.create()
* ```
*/
var Sandbox = class extends SandboxApi {
/**
* Use {@link Sandbox.create} to create a new Sandbox instead.
*
* @hidden
* @hide
* @internal
* @access protected
*/
constructor(opts) {
var _opts$sandboxDomain, _this$connectionConfi3, _this$connectionConfi4;
super();
_defineProperty(this, "files", void 0);
_defineProperty(this, "commands", void 0);
_defineProperty(this, "pty", void 0);
_defineProperty(this, "git", void 0);
_defineProperty(this, "sandboxId", void 0);
_defineProperty(this, "sandboxDomain", void 0);
_defineProperty(this, "trafficAccessToken", void 0);
_defineProperty(this, "envdPort", 49983);
_defineProperty(this, "mcpPort", 50005);
_defineProperty(this, "connectionConfig", void 0);
_defineProperty(this, "envdAccessToken", void 0);
_defineProperty(this, "envdApiUrl", void 0);
_defineProperty(this, "envdDirectUrl", void 0);
_defineProperty(this, "envdApi", void 0);
_defineProperty(this, "mcpToken", void 0);
this.connectionConfig = new ConnectionConfig(opts);
this.sandboxId = opts.sandboxId;
this.sandboxDomain = (_opts$sandboxDomain = opts.sandboxDomain) !== null && _opts$sandboxDomain !== void 0 ? _opts$sandboxDomain : this.connectionConfig.domain;
this.envdAccessToken = opts.envdAccessToken;
this.trafficAccessToken = opts.trafficAccessToken;
this.envdApiUrl = this.connectionConfig.getSandboxUrl(this.sandboxId, {
sandboxDomain: this.sandboxDomain,
envdPort: this.envdPort
});
this.envdDirectUrl = this.connectionConfig.getSandboxDirectUrl(this.sandboxId, {
sandboxDomain: this.sandboxDomain,
envdPort: this.envdPort
});
const sandboxHeaders = {
"E2b-Sandbox-Id": this.sandboxId,
"E2b-Sandbox-Port": this.envdPort.toString()
};
const envdFetch = createEnvdFetch(this.connectionConfig);
const envdRpcFetch = createEnvdRpcFetch(this.connectionConfig);
const interceptors = (opts === null || opts === void 0 ? void 0 : opts.logger) ? [createRpcLogger(opts.logger)] : void 0;
const rpcFetch = (url, options) => {
var _this$connectionConfi, _this$connectionConfi2, _options;
const headers = new Headers({ "User-Agent": (_this$connectionConfi = (_this$connectionConfi2 = this.connectionConfig.headers) === null || _this$connectionConfi2 === void 0 ? void 0 : _this$connectionConfi2["User-Agent"]) !== null && _this$connectionConfi !== void 0 ? _this$connectionConfi : "" });
new Headers(options === null || options === void 0 ? void 0 : options.headers).forEach((value, key) => headers.append(key, value));
new Headers(sandboxHeaders).forEach((value, key) => headers.append(key, value));
if (this.envdAccessToken) headers.append("X-Access-Token", this.envdAccessToken);
options = _objectSpread2(_objectSpread2({}, (_options = options) !== null && _options !== void 0 ? _options : {}), {}, {
headers,
redirect: "follow"
});
return envdRpcFetch(url, options).then(rejectProxyUnavailableResponse);
};
const rpcTransport = (0, _connectrpc_connect_web.createConnectTransport)({
baseUrl: this.envdApiUrl,
useBinaryFormat: false,
interceptors,
fetch: rpcFetch
});
const rpcStreamingTransport = createClientStreamingTransport({
baseUrl: this.envdApiUrl,
fetch: rpcFetch,
interceptors
});
this.envdApi = new EnvdApiClient({
apiUrl: this.envdApiUrl,
logger: opts === null || opts === void 0 ? void 0 : opts.logger,
envdAccessToken: this.envdAccessToken,
headers: _objectSpread2({ "User-Agent": (_this$connectionConfi3 = (_this$connectionConfi4 = this.connectionConfig.headers) === null || _this$connectionConfi4 === void 0 ? void 0 : _this$connectionConfi4["User-Agent"]) !== null && _this$connectionConfi3 !== void 0 ? _this$connectionConfi3 : "" }, sandboxHeaders),
fetch: (request) => envdFetch(request)
}, { version: opts.envdVersion });
this.files = new Filesystem(rpcTransport, this.envdApi, this.connectionConfig);
this.commands = new Commands(rpcTransport, this.envdApi, this.connectionConfig, rpcStreamingTransport);
this.pty = new Pty(rpcTransport, this.envdApi, this.connectionConfig, rpcStreamingTransport);
this.git = new Git(this.commands);
}
/**
* List sandboxes.
*
* By default (no `query.state` set in `opts`), returns sandboxes in both
* `running` and `paused` states. To filter by state, pass
* `opts.query.state = [...]`.
*
* @param opts connection options, plus optional `query` to filter by
* metadata / state / start time / template, `order` to sort by start
* time across the whole result set (not within a page), and `limit` /
* `nextToken` for pagination.
*
* @returns a {@link SandboxPaginator} that yields pages of sandboxes
* (running and paused by default). Iterate pages via
* `await paginator.nextItems()` while `paginator.hasNext` is `true`.
*/
static list(opts) {
return new SandboxPaginator(this.resolveOpts(opts));
}
static async create(templateOrOpts, opts) {
var _this = this;
var _templateOrOpts$templ;
const { template, sandboxOpts } = typeof templateOrOpts === "string" ? {
template: templateOrOpts,
sandboxOpts: opts
} : {
template: (_templateOrOpts$templ = templateOrOpts === null || templateOrOpts === void 0 ? void 0 : templateOrOpts.template) !== null && _templateOrOpts$templ !== void 0 ? _templateOrOpts$templ : (templateOrOpts === null || templateOrOpts === void 0 ? void 0 : templateOrOpts.mcp) ? _this.defaultMcpTemplate : _this.defaultTemplate,
sandboxOpts: templateOrOpts
};
const apiOpts = _this.resolveOpts(sandboxOpts);
const config = new ConnectionConfig(apiOpts);
if (config.debug) return new _this(_objectSpread2({
sandboxId: "debug_sandbox_id",
envdVersion: ENVD_DEBUG_FALLBACK
}, config));
const sandbox = new _this(_objectSpread2(_objectSpread2({}, await _this.createSandbox(template, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.timeoutMs, apiOpts)), config));
if (sandboxOpts === null || sandboxOpts === void 0 ? void 0 : sandboxOpts.mcp) {
sandbox.mcpToken = crypto.randomUUID();
try {
var _sandbox$mcpToken;
await sandbox.commands.run(`mcp-gateway --config ${shellQuote(JSON.stringify(sandboxOpts.mcp))}`, {
user: "root",
envs: { GATEWAY_ACCESS_TOKEN: (_sandbox$mcpToken = sandbox.mcpToken) !== null && _sandbox$mcpToken !== void 0 ? _sandbox$mcpToken : "" }
});
} catch (error) {
await sandbox.kill().catch(() => void 0);
if (error instanceof CommandExitError) throw new SandboxError(`Failed to start MCP gateway: ${error.stderr}`);
throw error;
}
}
return sandbox;
}
/**
* Connect to a sandbox. If the sandbox is paused, it will be automatically resumed.
* Sandbox must be either running or be paused.
*
* With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
*
* @param sandboxId sandbox ID.
* @param opts connection options.
*
* @returns A running sandbox instance
*
* @throws {@link GuestUnresponsiveError} when the paused sandbox's snapshot was taken
* while its guest agent was not responding (`envdUnresponsiveAtPause`); connect with `onResume: 'reboot'`.
*
* @example
* ```ts
* const sandbox = await Sandbox.create()
* const sandboxId = sandbox.sandboxId
*
* // Connect to the same sandbox.
* const sameSandbox = await Sandbox.connect(sandboxId)
* ```
*/
static async connect(sandboxId, opts) {
var _this2 = this;
const apiOpts = _this2.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
if (config.debug) return new _this2(_objectSpread2({
sandboxId,
envdVersion: ENVD_DEBUG_FALLBACK
}, config));
const sandbox = await _this2.connectSandbox(sandboxId, apiOpts);
return new _this2(_objectSpread2({
sandboxId,
sandboxDomain: sandbox.sandboxDomain,
envdAccessToken: sandbox.envdAccessToken,
trafficAccessToken: sandbox.trafficAccessToken,
envdVersion: sandbox.envdVersion
}, config));
}
/**
* Fork a running sandbox specified by sandbox ID.
*
* The sandbox is checkpointed in place (briefly paused, snapshotted with its
* full memory state, and resumed — its ID and expiration stay untouched) and
* `count` new sandboxes are created from that snapshot. All forks boot from
* the same snapshot, so the snapshot is captured once regardless of count.
*
* Each fork succeeds or fails independently — the returned array contains
* one entry per requested fork, either a running {@link Sandbox} instance or
* an `Error` describing why that fork failed to start
* (`Promise.allSettled`-style). Per-fork error codes map to the same error
* classes as other API errors (e.g. 429 to `RateLimitError`).
*
* @param sandboxId sandbox ID.
* @param opts fork options — `count`, `timeoutMs` and connection options.
*
* @returns array with one entry per requested fork — a sandbox instance or an error.
*
* @example
* ```ts
* const sandbox = await Sandbox.create()
*
* const [fork1, fork2] = await Sandbox.fork(sandbox.sandboxId, { count: 2 })
* if (fork1 instanceof Sandbox) {
* await fork1.commands.run('echo "hello from fork"')
* }
* ```
*/
static async fork(sandboxId, opts) {
var _this3 = this;
const apiOpts = _this3.resolveOpts(opts);
const config = new ConnectionConfig(apiOpts);
return (await _this3.forkSandbox(sandboxId, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.timeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.count, apiOpts)).map((result) => result instanceof Error ? result : new _this3(_objectSpread2(_objectSpread2({}, result), config)));
}
/**
* Connect to a sandbox. If the sandbox is paused, it will be automatically resumed.
* Sandbox must be either running or be paused.
*
* With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
*
* @param opts connection options.
*
* @returns A running sandbox instance
*
* @throws {@link GuestUnresponsiveError} when the paused sandbox's snapshot was taken
* while its guest agent was not responding (`envdUnresponsiveAtPause`); connect with `onResume: 'reboot'`.
*
* @example
* ```ts
* const sandbox = await Sandbox.create()
* await sandbox.pause()
*
* // Connect to the same sandbox.
* const sameSandbox = await sandbox.connect()
* ```
*/
async connect(opts) {
var _this4 = this;
if (_this4.connectionConfig.debug) return _this4;
await SandboxApi.connectSandbox(_this4.sandboxId, _this4.resolveApiOpts(opts));
return _this4;
}
/**
* Fork the sandbox.
*
* The sandbox is checkpointed in place (briefly paused, snapshotted with its
* full memory state, and resumed — its ID and expiration stay untouched) and
* `count` new sandboxes are created from that snapshot. All forks boot from
* the same snapshot, so the snapshot is captured once regardless of count.
*
* Each fork succeeds or fails independently — the returned array contains
* one entry per requested fork, either a running {@link Sandbox} instance or
* an `Error` describing why that fork failed to start
* (`Promise.allSettled`-style). Per-fork error codes map to the same error
* classes as other API errors (e.g. 429 to `RateLimitError`).
*
* @param opts fork options — `count`, `timeoutMs` and connection options.
*
* @returns array with one entry per requested fork — a sandbox instance or an error.
*
* @example
* ```ts
* const sandbox = await Sandbox.create()
*
* const [fork1, fork2] = await sandbox.fork({ count: 2 })
* if (fork1 instanceof Sandbox) {
* await fork1.commands.run('echo "hello from fork"')
* }
* ```
*/
async fork(opts) {
var _this5 = this;
return await _this5.constructor.fork(_this5.sandboxId, _this5.resolveApiOpts(opts));
}
/**
* Get the host address for the specified sandbox port.
* You can then use this address to connect to the sandbox port from outside the sandbox via HTTP or WebSocket.
*
* @param port number of the port in the sandbox.
*
* @returns host address of the sandbox port.
*
* @example
* ```ts
* const sandbox = await Sandbox.create()
* // Start an HTTP server
* await sandbox.commands.run('python3 -m http.server 3000', { background: true })
* // Get the hostname of the HTTP server
* const serverURL = sandbox.getHost(3000)
* ```
*/
getHost(port) {
return this.connectionConfig.getHost(this.sandboxId, port, this.sandboxDomain);
}
/**
* Check if the sandbox is running.
*
* @returns `true` if the sandbox is running, `false` otherwise.
*
* @example
* ```ts
* const sandbox = await Sandbox.create()
* await sandbox.isRunning() // Returns true
*
* await sandbox.kill()
* await sandbox.isRunning() // Returns false
* ```
*/
async isRunning(opts) {
var _this6 = this;
const signal = _this6.connectionConfig.getSignal(opts === null || opts === void 0 ? void 0 : opts.requestTimeoutMs, opts === null || opts === void 0 ? void 0 : opts.signal);
const res = await _this6.envdApi.api.GET("/health", { signal });
if (res.response.status == 502) return false;
const err = await handleEnvdApiError(res);
if (err) throw err;
return true;
}
/**
* Set the timeout of the sandbox.
*
* This method can extend or reduce the sandbox timeout set when creating the sandbox or from the last call to `.setTimeout`.
* Maximum time a sandbox can be kept alive is 24 hours (86_400_000 milliseconds) for Pro users and 1 hour (3_600_000 milliseconds) for Hobby users.
*
* @param timeoutMs timeout in **milliseconds**.
* @param opts connection options.
*/
async setTimeout(timeoutMs, opts) {
var _this7 = this;
if (_this7.connectionConfig.debug) return;
await SandboxApi.setTimeout(_this7.sandboxId, timeoutMs, _this7.resolveApiOpts(opts));
}
/**
* Update the network configuration of the sandbox.
*
* Replaces the current egress configuration atomically — fields that are
* omitted are cleared on the server.
*
* @param network new network configuration.
* @param opts connection options.
*/
async updateNetwork(network, opts) {
var _this8 = this;
await SandboxApi.updateNetwork(_this8.sandboxId, network, _this8.resolveApiOpts(opts));
}
/**
* Kill the sandbox.
*
* @param opts connection options.
*
* @returns `true` if the sandbox was killed, `false` if the sandbox was not found.
*/
async kill(opts) {
var _this9 = this;
if (_this9.connectionConfig.debug) return true;
return await SandboxApi.kill(_this9.sandboxId, _this9.resolveApiOpts(opts));
}
/**
* Pause a sandbox by its ID.
*
* @param opts connection options, plus `mode` to control the snapshot kind.
* With `mode: 'filesystem'`, the in-memory state is dropped and only the
* filesystem is persisted; resuming such a sandbox cold-boots (reboots) it
* from disk, losing running processes and open connections. Defaults to
* `'full'` (memory and filesystem). Pass `strict: true` to refuse the pause
* when the sandbox's guest agent does not respond.
*
* @returns `true` if the sandbox got paused, `false` if the sandbox was already paused.
*
* @throws {@link ServiceBusyError} when `strict` is set and the sandbox's
* guest agent does not respond; the sandbox keeps running.
*
* @example
* ```ts
* const sandbox = await Sandbox.create()
* await sandbox.pause()
*
* // filesystem-only snapshot (resume reboots the sandbox)
* await sandbox.pause({ mode: 'filesystem' })
*
* // refuse the pause if the guest agent is unresponsive
* await sandbox.pause({ strict: true })
* ```
*/
async pause(opts) {
var _this10 = this;
return await SandboxApi.pause(_this10.sandboxId, _this10.resolveApiOpts(opts));
}
/**
* @deprecated Use {@link Sandbox.pause} instead.
*/
async betaPause(opts) {
var _this11 = this;
return await SandboxApi.betaPause(_this11.sandboxId, _this11.resolveApiOpts(opts));
}
/**
* Create a snapshot of the sandbox's current state.
*
* The sandbox will be paused while the snapshot is being created.
* The snapshot can be used to create new sandboxes with the same filesystem and state.
* Snapshots are persistent and survive sandbox deletion.
*
* Use the returned `snapshotId` with `Sandbox.create(snapshotId)` to create a new sandbox from the snapshot.
*
* @param opts snapshot creation options including optional name, `mode`
* (`'filesystem'` takes a filesystem-only snapshot; sandboxes created from it
* cold-boot instead of restoring memory), and connection options.
*
* @returns snapshot information including the snapshot ID.
*
* @throws {@link SandboxError} with `statusCode` 400 when `mode: 'filesystem'`
* is requested but the feature is not enabled for the team
* (`snapshot_filesystem_only_disabled`), and with `statusCode` 409 when the
* sandbox's node runs an orchestrator that predates the option
* (`snapshot_filesystem_only_unsupported_node`). A full memory snapshot
* still works in both cases; for 409, pause and resume the sandbox and retry.
*
* @example
* ```ts
* const sandbox = await Sandbox.create()
* await sandbox.files.write('/app/state.json', '{"step": 1}')
*
* // Create a snapshot
* const snapshot = await sandbox.createSnapshot({ name: 'my-snapshot' })
*
* // Create a new sandbox from the snapshot
* const newSandbox = await Sandbox.create(snapshot.snapshotId)
*
* // Filesystem-only snapshot: sandboxes created from it start fresh from disk
* const fsSnapshot = await sandbox.createSnapshot({ mode: 'filesystem' })
* ```
*/
async createSnapshot(opts) {
var _this12 = this;
return await SandboxApi.createSnapshot(_this12.sandboxId, _objectSpread2(_objectSpread2({}, _this12.resolveApiOpts(opts)), {}, {
name: opts === null || opts === void 0 ? void 0 : opts.name,
mode: opts === null || opts === void 0 ? void 0 : opts.mode
}));
}
/**
* List all snapshots created from this sandbox.
*
* @param opts list options.
*
* @returns paginator for listing snapshots from this sandbox.
*/
listSnapshots(opts) {
return SandboxApi.listSnapshots(_objectSpread2(_objectSpread2({}, this.resolveApiOpts(opts)), {}, { sandboxId: this.sandboxId }));
}
/**
*
* Get the MCP URL for the sandbox.
*
* @returns MCP URL for the sandbox.
*/
getMcpUrl() {
return `https://${this.getHost(this.mcpPort)}/mcp`;
}
/**
* Get the MCP token for the sandbox.
*
* @returns MCP token for the sandbox, or undefined if MCP is not enabled.
*/
async getMcpToken() {
var _this13 = this;
if (!_this13.mcpToken) _this13.mcpToken = await _this13.files.read("/etc/mcp-gateway/.token", { user: "root" });
return _this13.mcpToken;
}
/**
* Get the URL to upload a file to the sandbox.
*
* You have to send a POST request to this URL with the file as multipart/form-data.
*
* @param path path to the file in the sandbox.
*
* @param opts upload url options.
*
* @returns URL for uploading file.
*/
async uploadUrl(path, opts) {
var _this14 = this;
var _opts;
opts = (_opts = opts) !== null && _opts !== void 0 ? _opts : {};
const useSignature = !!_this14.envdAccessToken;
if (!useSignature && opts.useSignatureExpiration != void 0) throw new InvalidArgumentError("Signature expiration can be used only when sandbox is created as secured.");
let username = opts.user;
if (username == void 0 && (0, compare_versions.compareVersions)(_this14.envdApi.version, "0.4.0") < 0) username = defaultUsername;
const filePath = path !== null && path !== void 0 ? path : "";
const fileUrl = _this14.fileUrl(filePath, username);
if (useSignature) {
const url = new URL(fileUrl);
const sig = await getSignature({
path: filePath,
operation: "write",
user: username,
expirationInSeconds: opts.useSignatureExpiration,
envdAccessToken: _this14.envdAccessToken
});
url.searchParams.set("signature", sig.signature);
if (sig.expiration) url.searchParams.set("signature_expiration", sig.expiration.toString());
return url.toString();
}
return fileUrl;
}
/**
* Get the URL to download a file from the sandbox.
*
* @param path path to the file in the sandbox.
*
* @param opts download url options.
*
* @returns URL for downloading file.
*/
async downloadUrl(path, opts) {
var _this15 = this;
var _opts2;
opts = (_opts2 = opts) !== null && _opts2 !== void 0 ? _opts2 : {};
const useSignature = !!_this15.envdAccessToken;
if (!useSignature && opts.useSignatureExpiration != void 0) throw new InvalidArgumentError("Signature expiration can be used only when sandbox is created as secured.");
let username = opts.user;
if (username == void 0 && (0, compare_versions.compareVersions)(_this15.envdApi.version, "0.4.0") < 0) username = defaultUsername;
const fileUrl = _this15.fileUrl(path, username);
if (useSignature) {
const url = new URL(fileUrl);
const sig = await getSignature({
path,
operation: "read",
user: username,
expirationInSeconds: opts.useSignatureExpiration,
envdAccessToken: _this15.envdAccessToken
});
url.searchParams.set("signature", sig.signature);
if (sig.expiration) url.searchParams.set("signature_expiration", sig.expiration.toString());
return url.toString();
}
return fileUrl;
}
/**
* Get sandbox information like sandbox ID, template, metadata, started at/end at date.
*
* @param opts connection options.
*
* @returns information about the sandbox
*/
async getInfo(opts) {
var _this16 = this;
return await SandboxApi.getInfo(_this16.sandboxId, _this16.resolveApiOpts(opts));
}
/**
* Get the metrics of the sandbox.
*
* @param opts connection options.
*
* @returns List of sandbox metrics containing CPU, memory and disk usage information.
*/
async getMetrics(opts) {
var _this17 = this;
if (_this17.connectionConfig.debug) return [];
if (_this17.envdApi.version) {
if ((0, compare_versions.compareVersions)(_this17.envdApi.version, "0.1.5") < 0) throw new TemplateError("You need to update the template to use the new SDK.");
if ((0, compare_versions.compareVersions)(_this17.envdApi.version, "0.2.4") < 0) {
var _this$connectionConfi5, _this$connectionConfi6;
(_this$connectionConfi5 = _this17.connectionConfig.logger) === null || _this$connectionConfi5 === void 0 || (_this$connectionConfi6 = _this$connectionConfi5.warn) === null || _this$connectionConfi6 === void 0 || _this$connectionConfi6.call(_this$connectionConfi5, "Disk metrics are not supported in this version of the sandbox, please rebuild the template to get disk metrics.");
}
}
return await SandboxApi.getMetrics(_this17.sandboxId, _this17.resolveApiOpts(opts));
}
resolveApiOpts(opts) {
return ConnectionConfig.mergeOpts(this.connectionConfig, opts);
}
fileUrl(path, username) {
const url = new URL("/files", this.envdDirectUrl);
if (username) url.searchParams.set("username", username);
if (path) url.searchParams.set("path", path);
return url.toString();
}
};
_defineProperty(Sandbox, "defaultTemplate", "base");
_defineProperty(Sandbox, "defaultMcpTemplate", "mcp-gateway");
//#endregion
//#region src/template/callable.ts
/**
* Make a template class callable as a factory, so `Template(opts)` keeps
* returning a builder, and keep the statics usable when they are pulled off the
* class on their own (`const { build } = Template`). Everything else —
* construction, `instanceof`, subclassing — goes straight to the class.
*
* @internal
* @hidden
* @hide
*/
function callableTemplate(cls) {
const bound = /* @__PURE__ */ new WeakMap();
return new Proxy(cls, {
apply(target, _thisArg, args) {
return new target(...args);
},
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
if (prop === "prototype" || typeof value !== "function") return value;
const self = typeof receiver === "function" ? receiver : target;
let methods = bound.get(self);
if (!methods) {
methods = /* @__PURE__ */ new Map();
bound.set(self, methods);
}
let method = methods.get(prop);
if (!method) {
method = value.bind(self);
methods.set(prop, method);
}
return method;
}
});
}
//#endregion
//#region src/template/types.ts
function convertTemplateInfo(template) {
var _template$isDefault;
return _objectSpread2(_objectSpread2(_objectSpread2({
templateId: template.templateID,
buildId: template.buildID,
names: template.names
}, template.description && { description: template.description }), {}, {
public: template.public,
isDefault: (_template$isDefault = template.isDefault) !== null && _template$isDefault !== void 0 ? _template$isDefault : false,
buildStatus: template.buildStatus,
cpuCount: template.cpuCount
}, template.maxCpuCount != null && { maxCpuCount: template.maxCpuCount }), {}, {
memoryMB: template.memoryMB,
diskSizeMB: template.diskSizeMB,
envdVersion: template.envdVersion,
buildCount: template.buildCount,
spawnCount: template.spawnCount,
createdAt: new Date(template.createdAt),
updatedAt: new Date(template.updatedAt)
}, template.lastSpawnedAt && { lastSpawnedAt: new Date(template.lastSpawnedAt) });
}
//#endregion
//#region src/template/paginator.ts
/**
* Paginator for listing templates.
*
* @example
* ```ts
* const paginator = Template.list()
* while (paginator.hasNext) {
* const templates = await paginator.nextItems()
* console.log(templates)
* }
* ```
*/
var TemplatePaginator = class extends Paginator {
constructor(opts) {
super(opts, opts === null || opts === void 0 ? void 0 : opts.limit, opts === null || opts === void 0 ? void 0 : opts.nextToken);
}
async nextItems(opts) {
var _this = this;
var _res$data;
if (!_this.hasNext) throw new Error("No more items to fetch");
const apiOpts = ConnectionConfig.mergeOpts(_this.opts, opts);
const config = new ConnectionConfig(apiOpts);
const res = await new ApiClient(config).api.GET("/v2/templates", {
params: { query: {
limit: _this.limit,
nextToken: _this.nextToken
} },
signal: config.getSignal(apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.requestTimeoutMs, apiOpts === null || apiOpts === void 0 ? void 0 : apiOpts.signal)
});
const err = handleApiError(res, TemplateError);
if (err) throw err;
_this.updatePagination(res.response);
return ((_res$data = res.data) !== null && _res$data !== void 0 ? _res$data : []).map(convertTemplateInfo);
}
};
//#endregion
//#region src/template/readycmd.ts
/**
* Class for ready check commands.
*/
var ReadyCmd = class {
constructor(cmd) {
_defineProperty(this, "cmd", void 0);
this.cmd = cmd;
}
getCmd() {
return this.cmd;
}
};
/**
* Wait for a port to be listening.
* Uses `ss` command to check if a port is open and listening.
*
* @param port Port number to wait for
* @returns ReadyCmd that checks for the port
*
* @example
* ```ts
* import { Template, waitForPort } from 'e2b'
*
* const template = Template()
* .fromPythonImage()
* .setStartCmd('python -m http.server 8000', waitForPort(8000))
* ```
*/
function waitForPort(port) {
return new ReadyCmd(`[ -n "$(ss -Htuln sport = :${port})" ]`);
}
/**
* Wait for a URL to return a specific HTTP status code.
* Uses `curl` to make HTTP requests and check the response status.
*
* @param url URL to check (e.g., 'http://localhost:3000/health')
* @param statusCode Expected HTTP status code (default: 200)
* @returns ReadyCmd that checks the URL
*
* @example
* ```ts
* import { Template, waitForURL } from 'e2b'
*
* const template = Template()
* .fromNodeImage()
* .setStartCmd('npm start', waitForURL('http://localhost:3000/health'))
* ```
*/
function waitForURL(url, statusCode = 200) {
return new ReadyCmd(`curl -s -o /dev/null -w "%{http_code}" ${shellQuote(url)} | grep -q "${statusCode}"`);
}
/**
* Wait for a process with a specific name to be running.
* Uses `pgrep` to check if a process exists.
*
* @param processName Name of the process to wait for
* @returns ReadyCmd that checks for the process
*
* @example
* ```ts
* import { Template, waitForProcess } from 'e2b'
*
* const template = Template()
* .fromBaseImage()
* .setStartCmd('./my-daemon', waitForProcess('my-daemon'))
* ```
*/
function waitForProcess(processName) {
return new ReadyCmd(`pgrep ${shellQuote(processName)} > /dev/null`);
}
/**
* Wait for a file to exist.
* Uses shell test command to check file existence.
*
* @param filename Path to the file to wait for
* @returns ReadyCmd that checks for the file
*
* @example
* ```ts
* import { Template, waitForFile } from 'e2b'
*
* const template = Template()
* .fromBaseImage()
* .setStartCmd('./init.sh', waitForFile('/tmp/ready'))
* ```
*/
function waitForFile(filename) {
return new ReadyCmd(`[ -f ${shellQuote(filename)} ]`);
}
/**
* Wait for a specified timeout before considering the sandbox ready.
* Uses `sleep` command to wait for a fixed duration.
*
* @param timeout Time to wait in milliseconds (minimum: 1000ms / 1 second)
* @returns ReadyCmd that waits for the specified duration
*
* @example
* ```ts
* import { Template, waitForTimeout } from 'e2b'
*
* const template = Template()
* .fromNodeImage()
* .setStartCmd('npm start', waitForTimeout(5000)) // Wait 5 seconds
* ```
*/
function waitForTimeout(timeout) {
return new ReadyCmd(`sleep ${Math.max(1, Math.floor(timeout / 1e3))}`);
}
//#endregion
//#region src/template/dockerfileParser.ts
const RUN_FLAGS = {
mount: {
type: "string",
ignored: true
},
network: {
type: "string",
ignored: true
},
security: {
type: "string",
ignored: true
},
device: {
type: "string",
ignored: true
}
};
const COPY_FLAGS = {
chown: { type: "string" },
chmod: { type: "string" },
from: { type: "string" },
link: {
type: "bool",
ignored: true
},
exclude: {
type: "string",
ignored: true
},
parents: {
type: "bool",
ignored: true
}
};
const ADD_FLAGS = _objectSpread2(_objectSpread2({}, COPY_FLAGS), {}, {
"keep-git-dir": {
type: "bool",
ignored: true
},
checksum: {
type: "string",
ignored: true
},
unpack: {
type: "bool",
ignored: true
}
});
const FROM_FLAGS = { platform: {
type: "string",
ignored: true
} };
const IGNORED_INSTRUCTIONS = /* @__PURE__ */ new Set([
"EXPOSE",
"VOLUME",
"LABEL",
"MAINTAINER"
]);
/** A heredoc delimiter that does not occur in the content it wraps. */
function heredocTerminator(name, content) {
let terminator = `E2B_HEREDOC_${name.replace(/[^A-Za-z0-9_]/g, "_")}`;
while (content.includes(terminator)) terminator += "_";
return terminator;
}
function withTrailingNewline(content) {
return content === "" || content.endsWith("\n") ? content : content + "\n";
}
const VARIABLE_NAME = new RegExp("\\p{Nd}+|[@*#?\\-$!0]|[\\p{L}\\p{Nd}_]+", "uy");
function shellLiteral(ch, stopChar) {
return ch === "\\" || ch === "`" || ch === "$" || ch === stopChar ? "\\" + ch : ch;
}
/**
* Rewrite an expandable heredoc body so that the shell applies Docker's
* rules: `$VAR` / `${VAR...}` are substituted, a backslash escapes the next
* character, and everything else - `$(...)` and backticks included - is
* literal. Backslashes inside `#`, `%` and `/` patterns stay significant to
* the shell's pattern matching, as they do for Docker.
*/
function expandableHeredocBody(content, line) {
let i = 0;
function missing(stopChar) {
throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`syntax error: missing ${stopChar === "/" ? "'/' in ${}" : "'}'"}`, line);
}
function name() {
VARIABLE_NAME.lastIndex = i;
const match = VARIABLE_NAME.exec(content);
if (!match) return "";
i += match[0].length;
return match[0];
}
function variable() {
if (content[i] !== "{") {
const varName = name();
return varName === "" ? "\\$" : "$" + varName;
}
i++;
if (i >= content.length) missing("}");
if (content[i] === "{" || content[i] === "}" || content[i] === ":") throw new _e2b_dockerfile_utils.DockerfileSyntaxError("syntax error: bad substitution", line);
const varName = name();
if (i >= content.length) missing("}");
let modifier = content[i++];
if (modifier === "}") return `\${${varName}}`;
if (modifier === "/") {
if (content[i] === "/") modifier += content[i++];
const pattern = scan("/", true);
return `\${${varName}${modifier}${pattern}/${scan("}", true)}}`;
}
if (modifier === ":") {
if (i >= content.length) missing("}");
modifier += content[i++];
}
const op = modifier[modifier.length - 1];
const isPattern = op === "#" || op === "%";
if (!"+-?#%".includes(op) || modifier.length === 2 && isPattern) throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`unsupported modifier (${modifier}) in substitution`, line);
return `\${${varName}${modifier}${scan("}", isPattern)}}`;
}
function scan(stopChar, rawEscapes = false) {
let out = "";
while (i < content.length) {
const ch = content[i++];
if (ch === stopChar) return out;
if (ch === "\\") {
if (i < content.length) {
const next = content[i++];
out += rawEscapes ? "\\" + next : shellLiteral(next, stopChar);
}
} else if (ch === "$") out += variable();
else out += ch === "`" ? "\\`" : ch;
}
if (stopChar !== void 0) missing(stopChar);
return out;
}
return scan();
}
function shellJoin(words) {
return words.map(shellQuote).join(" ");
}
var DockerfileConverter = class {
constructor(templateBuilder, escapeToken) {
this.templateBuilder = templateBuilder;
_defineProperty(this, "lex", void 0);
_defineProperty(this, "envLex", void 0);
_defineProperty(this, "vars", /* @__PURE__ */ new Map());
_defineProperty(this, "userChanged", false);
_defineProperty(this, "workdirChanged", false);
_defineProperty(this, "cmd", void 0);
_defineProperty(this, "entrypoint", void 0);
this.lex = new _e2b_dockerfile_utils.ShellLex(escapeToken);
this.envLex = new _e2b_dockerfile_utils.ShellLex(escapeToken, {
env: this.vars,
skipUnsetEnv: false
});
}
convert(instructions) {
const fromInstructions = instructions.filter((i) => i.name === "FROM");
if (fromInstructions.length > 1) throw new _e2b_dockerfile_utils.DockerfileSyntaxError("Multi-stage Dockerfiles are not supported");
if (fromInstructions.length === 0) throw new _e2b_dockerfile_utils.DockerfileSyntaxError("Dockerfile must contain a FROM instruction");
const fromInstruction = fromInstructions[0];
const baseImage = this.handleFrom(fromInstruction);
this.templateBuilder.setUser("root");
this.templateBuilder.setWorkdir("/");
for (const instruction of instructions) {
if (instruction.name === "FROM") continue;
if (instruction.startLine < fromInstruction.startLine && instruction.name !== "ARG") throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`${instruction.name} must be preceded by a FROM instruction`, instruction.startLine);
this.handleInstruction(instruction);
}
this.applyStartCmd();
if (!this.userChanged) this.templateBuilder.setUser("user");
if (!this.workdirChanged) this.templateBuilder.setWorkdir("/home/user");
return { baseImage };
}
handleInstruction(instruction) {
switch (instruction.name) {
case "RUN":
this.handleRun(instruction);
break;
case "COPY":
this.handleCopy(instruction, COPY_FLAGS);
break;
case "ADD":
this.handleCopy(instruction, ADD_FLAGS);
break;
case "WORKDIR":
this.templateBuilder.setWorkdir(this.expandSingle(instruction));
this.workdirChanged = true;
break;
case "USER":
this.templateBuilder.setUser(this.expandSingle(instruction));
this.userChanged = true;
break;
case "ENV":
this.handleEnv(instruction);
break;
case "ARG":
this.handleArg(instruction);
break;
case "CMD":
this.parseFlags(instruction, {});
this.cmd = {
json: instruction.json,
args: instruction.args
};
break;
case "ENTRYPOINT":
this.parseFlags(instruction, {});
this.entrypoint = {
json: instruction.json,
args: instruction.args
};
break;
default: if (!IGNORED_INSTRUCTIONS.has(instruction.name)) console.warn(`Unsupported instruction: ${instruction.name}`);
}
}
/** Resolve quotes and escapes in a word, keeping `$VAR` references. */
expand(instruction, word, lex = this.lex) {
try {
return lex.processWord(word);
} catch (err) {
throw new _e2b_dockerfile_utils.DockerfileSyntaxError(err instanceof Error ? err.message : String(err), instruction.startLine);
}
}
expandSingle(instruction) {
this.parseFlags(instruction, {});
if (instruction.args.length !== 1) throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`${instruction.name} requires exactly one argument`, instruction.startLine);
return this.expand(instruction, instruction.args[0]);
}
parseFlags(instruction, specs) {
const values = {};
for (const flag of instruction.flags) {
if (!flag.startsWith("--")) throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`arg should start with -- : ${flag}`, instruction.startLine);
const body = flag.slice(2);
const eq = body.indexOf("=");
const name = eq === -1 ? body : body.slice(0, eq);
const rawValue = eq === -1 ? void 0 : body.slice(eq + 1);
const spec = Object.hasOwn(specs, name) ? specs[name] : void 0;
if (!spec) throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`unknown flag: ${name}`, instruction.startLine);
if (name in values) throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`duplicate flag specified: ${name}`, instruction.startLine);
let value;
if (spec.type === "bool") {
if (rawValue === void 0 || rawValue === "" || rawValue === "true") value = "true";
else if (rawValue === "false") value = "false";
else throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`expecting boolean value for flag ${name}, not: ${rawValue}`, instruction.startLine);
} else {
if (rawValue === void 0) throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`missing a value on flag: ${name}`, instruction.startLine);
value = rawValue;
}
if (spec.ignored) {
console.warn(`Ignoring unsupported ${instruction.name} flag --${name} (line ${instruction.startLine})`);
continue;
}
values[name] = value;
}
return values;
}
handleFrom(instruction) {
this.parseFlags(instruction, FROM_FLAGS);
const args = instruction.args;
if (args.length === 3 && args[1].toLowerCase() === "as") {} else if (args.length !== 1) throw new _e2b_dockerfile_utils.DockerfileSyntaxError("FROM requires either one or three arguments", instruction.startLine);
return this.expand(instruction, args[0]);
}
handleRun(instruction) {
this.parseFlags(instruction, RUN_FLAGS);
const { args, json, heredocs } = instruction;
if (args.length === 0) throw new _e2b_dockerfile_utils.DockerfileSyntaxError("RUN requires at least one argument", instruction.startLine);
let command;
if (json) command = shellJoin(args);
else if (heredocs.length > 0) command = this.runWithHeredocs(args[0], heredocs);
else command = args[0];
this.templateBuilder.runCmd(command);
}
runWithHeredocs(line, heredocs) {
const heredocContent = (heredoc) => heredoc.chomp ? (0, _e2b_dockerfile_utils.chompHeredocContent)(heredoc.content) : heredoc.content;
if (heredocs.length === 1 && (0, _e2b_dockerfile_utils.parseHeredoc)(line.trim())) {
const heredoc = heredocs[0];
const content = heredocContent(heredoc);
if (!content.startsWith("#!")) return content;
const script = shellQuote(`/tmp/${heredoc.name}`);
const terminator = heredocTerminator(heredoc.name, content);
return `cat <<'${terminator}' >${script}\n` + withTrailingNewline(content) + `${terminator}\nchmod +x ${script} && ${script}\nE2B_EXIT=$?; rm -f ${script}; exit $E2B_EXIT`;
}
let full = line;
for (const heredoc of heredocs) full += "\n" + heredoc.content + heredoc.name;
return full;
}
handleCopy(instruction, specs) {
const flags = this.parseFlags(instruction, specs);
if (instruction.args.length < 2) throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`${instruction.name} requires at least two arguments`, instruction.startLine);
if (flags.from !== void 0) throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`${instruction.name} --from is not supported (multi-stage builds are not supported)`, instruction.startLine);
const user = flags.chown !== void 0 ? this.expand(instruction, flags.chown) : void 0;
let mode;
if (flags.chmod !== void 0) {
const chmod = this.expand(instruction, flags.chmod);
const resolved = this.expand(instruction, flags.chmod, this.envLex);
if (!/^[0-7]{3,4}$/.test(resolved)) {
const detail = resolved === chmod ? "" : ` (resolves to "${resolved}")`;
throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`invalid chmod value "${chmod}"${detail}, expected an octal mode such as 0755`, instruction.startLine);
}
mode = parseInt(resolved, 8);
}
const rawDest = instruction.args[instruction.args.length - 1];
if ((0, _e2b_dockerfile_utils.parseHeredoc)(rawDest)) throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`${instruction.name} cannot accept a heredoc as a destination`, instruction.startLine);
const dest = this.expand(instruction, rawDest);
const heredocsByName = new Map(instruction.heredocs.map((heredoc) => [heredoc.name, heredoc]));
for (const rawSrc of instruction.args.slice(0, -1)) {
const heredoc = (0, _e2b_dockerfile_utils.parseHeredoc)(rawSrc);
if (heredoc) {
const content = heredocsByName.get(heredoc.name);
if (!content) throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`missing heredoc content for ${heredoc.name}`, instruction.startLine);
this.copyHeredoc(content, dest, user, mode, instruction.startLine);
continue;
}
const src = this.expand(instruction, rawSrc);
if (instruction.name === "ADD" && /^[a-z][a-z0-9+.-]*:\/\//i.test(src)) throw new _e2b_dockerfile_utils.DockerfileSyntaxError(`ADD from a remote URL is not supported: ${src}`, instruction.startLine);
this.templateBuilder.copy(src, dest, {
user,
mode
});
}
}
/** `COPY <<EOF /path` writes the heredoc body to a file inside the sandbox. */
copyHeredoc(heredoc, dest, user, mode, line) {
let content = heredoc.chomp ? (0, _e2b_dockerfile_utils.chompHeredocContent)(heredoc.content) : heredoc.content;
if (heredoc.expand) content = expandableHeredocBody(content, line);
const plainTerminator = heredocTerminator(heredoc.name, content);
const terminator = heredoc.expand ? plainTerminator : `'${plainTerminator}'`;
const target = "\"$E2B_HEREDOC_DEST\"";
let command = dest.endsWith("/") ? `E2B_HEREDOC_DEST=${shellQuote(dest + heredoc.name)}\n` : `E2B_HEREDOC_DEST=${shellQuote(dest)}\nif [ -d ${target} ]; then E2B_HEREDOC_DEST=${target}/${shellQuote(heredoc.name)}; fi\n`;
command += `mkdir -p "$(dirname ${target})" && cat <<${terminator} >${target}\n` + withTrailingNewline(content) + plainTerminator;
if (user !== void 0) command += `\nchown ${shellQuote(user)} ${target}`;
if (mode !== void 0) command += `\nchmod ${mode.toString(8)} ${target}`;
this.templateBuilder.runCmd(command, { user: "root" });
}
handleEnv(instruction) {
this.parseFlags(instruction, {});
const { args } = instruction;
if (args.length === 0) throw new _e2b_dockerfile_utils.DockerfileSyntaxError("ENV requires at least one argument", instruction.startLine);
const envs = {};
for (let i = 0; i + 2 < args.length; i += 3) {
const key = this.expand(instruction, args[i]);
if (key === "") throw new _e2b_dockerfile_utils.DockerfileSyntaxError("ENV names can not be blank", instruction.startLine);
envs[key] = this.expand(instruction, args[i + 1]);
this.vars.set(key, this.expand(instruction, args[i + 1], this.envLex));
}
this.templateBuilder.setEnvs(envs);
}
handleArg(instruction) {
this.parseFlags(instruction, {});
const { args } = instruction;
if (args.length === 0) throw new _e2b_dockerfile_utils.DockerfileSyntaxError("ARG requires at least one argument", instruction.startLine);
const envs = {};
for (const arg of args) {
const eq = arg.indexOf("=");
const key = this.expand(instruction, eq === -1 ? arg : arg.slice(0, eq));
if (key === "") throw new _e2b_dockerfile_utils.DockerfileSyntaxError("ARG names can not be blank", instruction.startLine);
const value = arg.slice(eq + 1);
envs[key] = eq === -1 ? "" : this.expand(instruction, value);
this.vars.set(key, eq === -1 ? "" : this.expand(instruction, value, this.envLex));
}
this.templateBuilder.setEnvs(envs);
}
/**
* Combine ENTRYPOINT and CMD following Docker's rules:
* - exec-form ENTRYPOINT gets CMD appended as arguments
* (a shell-form CMD is appended as `/bin/sh -c <cmd>`),
* - shell-form ENTRYPOINT ignores CMD,
* - without ENTRYPOINT the CMD is used as-is.
*/
applyStartCmd() {
const { cmd, entrypoint } = this;
let command;
if (entrypoint) {
if (entrypoint.json) {
const words = [...entrypoint.args];
if (cmd) {
if (cmd.json) words.push(...cmd.args);
else if (cmd.args.length > 0) words.push("/bin/sh", "-c", cmd.args[0]);
}
command = words.length > 0 ? shellJoin(words) : void 0;
} else command = entrypoint.args.length > 0 ? entrypoint.args[0] : void 0;
} else if (cmd) {
if (cmd.json) command = cmd.args.length > 0 ? shellJoin(cmd.args) : void 0;
else command = cmd.args.length > 0 ? cmd.args[0] : void 0;
}
if (command !== void 0 && command.trim() !== "") this.templateBuilder.setStartCmd(command, waitForTimeout(2e4));
}
};
function readDockerfile(dockerfileContentOrPath) {
try {
if (node_fs.default.existsSync(dockerfileContentOrPath) && node_fs.default.statSync(dockerfileContentOrPath).isFile()) return node_fs.default.readFileSync(dockerfileContentOrPath, "utf-8");
} catch (_unused) {}
return dockerfileContentOrPath;
}
/**
* Parse a Dockerfile and convert it to Template SDK format
*
* @param dockerfileContentOrPath Either the Dockerfile content as a string,
* or a path to a Dockerfile file
* @param templateBuilder Interface providing template builder methods
* @returns Parsed Dockerfile result with base image and instructions
*/
function parseDockerfile(dockerfileContentOrPath, templateBuilder) {
const dockerfileContent = readDockerfile(dockerfileContentOrPath);
const ast = (0, _e2b_dockerfile_utils.parseDockerfileAst)(dockerfileContent);
for (const warning of ast.warnings) console.warn(`${warning.message} (line ${warning.line})`);
return new DockerfileConverter(templateBuilder, ast.escapeToken).convert(ast.instructions);
}
//#endregion
//#region src/template/index.ts
/**
* Builder for E2B sandbox templates, and the entrypoint for the template API.
*
* Exposed as {@link Template}, which can be called as a factory.
*/
var TemplateBase = class extends ClientFactory {
constructor(options) {
var _options$fileContextP, _getCallerDirectory, _options$fileIgnorePa;
super();
_defineProperty(this, "defaultBaseImage", "e2bdev/base");
_defineProperty(this, "baseImage", this.defaultBaseImage);
_defineProperty(this, "baseTemplate", void 0);
_defineProperty(this, "registryConfig", void 0);
_defineProperty(this, "startCmd", void 0);
_defineProperty(this, "readyCmd", void 0);
_defineProperty(this, "force", false);
_defineProperty(this, "forceNextLayer", false);
_defineProperty(this, "instructions", []);
_defineProperty(this, "fileContextPath", void 0);
_defineProperty(this, "fileIgnorePatterns", []);
_defineProperty(this, "logsRefreshFrequency", 200);
_defineProperty(this, "stackTraces", []);
this.fileContextPath = (_options$fileContextP = options === null || options === void 0 ? void 0 : options.fileContextPath) !== null && _options$fileContextP !== void 0 ? _options$fileContextP : runtime === "browser" ? "." : (_getCallerDirectory = getCallerDirectory()) !== null && _getCallerDirectory !== void 0 ? _getCallerDirectory : ".";
this.fileIgnorePatterns = (_options$fileIgnorePa = options === null || options === void 0 ? void 0 : options.fileIgnorePatterns) !== null && _options$fileIgnorePa !== void 0 ? _options$fileIgnorePa : this.fileIgnorePatterns;
}
/**
* Convert a template to JSON representation.
*
* @param template The template to convert
* @param computeHashes Whether to compute file hashes for cache invalidation
* @returns JSON string representation of the template
*/
static toJSON(template, computeHashes = true) {
return template.toJSON(computeHashes);
}
/**
* Convert a template to Dockerfile format.
* Note: Templates based on other E2B templates cannot be converted to Dockerfile.
*
* @param template The template to convert
* @returns Dockerfile string representation
* @throws Error if the template is based on another E2B template
*/
static toDockerfile(template) {
return template.toDockerfile();
}
static async build(template, nameOrOptions, options) {
var _this = this;
var _this$resolveOpts;
const { name, buildOptions } = normalizeBuildArguments(nameOrOptions, options);
const buildOpts = (_this$resolveOpts = _this.resolveOpts(buildOptions)) !== null && _this$resolveOpts !== void 0 ? _this$resolveOpts : {};
try {
var _buildOpts$onBuildLog, _buildOpts$onBuildLog2;
(_buildOpts$onBuildLog = buildOpts.onBuildLogs) === null || _buildOpts$onBuildLog === void 0 || _buildOpts$onBuildLog.call(buildOpts, new LogEntryStart(/* @__PURE__ */ new Date(), "Build started"));
const baseTemplate = template;
const config = new ConnectionConfig(buildOpts);
const client = new ApiClient(config);
const data = await baseTemplate.build(client, config, name, buildOpts);
(_buildOpts$onBuildLog2 = buildOpts.onBuildLogs) === null || _buildOpts$onBuildLog2 === void 0 || _buildOpts$onBuildLog2.call(buildOpts, new LogEntry(/* @__PURE__ */ new Date(), "info", "Waiting for logs..."));
await waitForBuildFinish(client, {
templateID: data.templateId,
buildID: data.buildId,
onBuildLogs: buildOpts.onBuildLogs,
logsRefreshFrequency: baseTemplate.logsRefreshFrequency,
stackTraces: baseTemplate.stackTraces,
signal: buildOpts.signal,
requestTimeoutMs: config.requestTimeoutMs
});
return data;
} finally {
var _buildOpts$onBuildLog3;
(_buildOpts$onBuildLog3 = buildOpts.onBuildLogs) === null || _buildOpts$onBuildLog3 === void 0 || _buildOpts$onBuildLog3.call(buildOpts, new LogEntryEnd(/* @__PURE__ */ new Date(), "Build finished"));
}
}
static async buildInBackground(template, nameOrOptions, options) {
var _this2 = this;
var _this$resolveOpts2;
const { name, buildOptions } = normalizeBuildArguments(nameOrOptions, options);
const buildOpts = (_this$resolveOpts2 = _this2.resolveOpts(buildOptions)) !== null && _this$resolveOpts2 !== void 0 ? _this$resolveOpts2 : {};
const config = new ConnectionConfig(buildOpts);
const client = new ApiClient(config);
return template.build(client, config, name, buildOpts);
}
/**
* Get the status of a build.
*
* @param data Build identifiers
* @param options Authentication options
*
* @example
* ```ts
* const status = await Template.getBuildStatus(data)
* ```
*/
static async getBuildStatus(data, options) {
const config = new ConnectionConfig(this.resolveOpts(options));
return await getBuildStatus(new ApiClient(config), {
templateID: data.templateId,
buildID: data.buildId,
logsOffset: options === null || options === void 0 ? void 0 : options.logsOffset
}, config.getSignal(void 0, options === null || options === void 0 ? void 0 : options.signal));
}
/**
* Check if a template with the given name exists.
*
* @param name Template name to check
* @param options Authentication options
* @returns True if the name exists, false otherwise
*
* @example
* ```ts
* const exists = await Template.exists('my-python-env')
* if (exists) {
* console.log('Template exists!')
* }
* ```
*/
static async exists(name, options) {
return this.aliasExists(name, options);
}
/**
* Check if a template with the given alias exists.
*
* @param alias Template alias to check
* @param options Authentication options
* @returns True if the alias exists, false otherwise
*
* @deprecated Use `exists` instead.
* @example
* ```ts
* const exists = await Template.aliasExists('my-python-env')
* if (exists) {
* console.log('Template exists!')
* }
* ```
*/
static async aliasExists(alias, options) {
const config = new ConnectionConfig(this.resolveOpts(options));
return checkAliasExists(new ApiClient(config), { alias }, config.getSignal(void 0, options === null || options === void 0 ? void 0 : options.signal));
}
/**
* Assign tag(s) to an existing template build.
*
* @param targetName Template name in 'name:tag' format (the source build to tag from)
* @param tags Tag or tags to assign
* @param options Authentication options
* @returns Tag info with buildId and assigned tags
*
* @example
* ```ts
* // Assign a single tag
* await Template.assignTags('my-template:v1.0', 'production')
*
* // Assign multiple tags
* await Template.assignTags('my-template:v1.0', ['production', 'stable'])
* ```
*/
static async assignTags(targetName, tags, options) {
const config = new ConnectionConfig(this.resolveOpts(options));
return assignTags(new ApiClient(config), {
targetName,
tags: Array.isArray(tags) ? tags : [tags]
}, config.getSignal(void 0, options === null || options === void 0 ? void 0 : options.signal));
}
/**
* Remove tag(s) from a template.
*
* @param name Template name
* @param tags Tag or tags to remove
* @param options Authentication options
*
* @example
* ```ts
* // Remove a single tag
* await Template.removeTags('my-template', 'production')
*
* // Remove multiple tags from a template
* await Template.removeTags('my-template', ['production', 'staging'])
* ```
*/
static async removeTags(name, tags, options) {
const config = new ConnectionConfig(this.resolveOpts(options));
return removeTags(new ApiClient(config), {
name,
tags: Array.isArray(tags) ? tags : [tags]
}, config.getSignal(void 0, options === null || options === void 0 ? void 0 : options.signal));
}
/**
* Get all tags for a template.
*
* @param templateId Template ID or name
* @param options Authentication options
* @returns Array of tag details including tag name, buildId, and creation date
*
* @example
* ```ts
* const tags = await Template.getTags('my-template')
* for (const tag of tags) {
* console.log(`Tag: ${tag.tag}, Build: ${tag.buildId}, Created: ${tag.createdAt}`)
* }
* ```
*/
static async getTags(templateId, options) {
const config = new ConnectionConfig(this.resolveOpts(options));
return getTemplateTags(new ApiClient(config), { templateID: templateId }, config.getSignal(void 0, options === null || options === void 0 ? void 0 : options.signal));
}
/**
* List the project's templates.
*
* @param opts connection and pagination options.
*
* @returns a {@link TemplatePaginator} that yields pages of templates.
*
* @example
* ```ts
* const paginator = Template.list({ limit: 50 })
* while (paginator.hasNext) {
* const templates = await paginator.nextItems()
* console.log(templates)
* }
* ```
*/
static list(opts) {
return new TemplatePaginator(this.resolveOpts(opts));
}
/**
* Delete a template or snapshot.
*
* @param nameOrId Template or snapshot name or ID
* @param options Authentication options
* @returns `true` if the template was deleted, `false` if it was not found
* @throws TemplateError if the API returns an error other than not found
*
* @example
* ```ts
* const deleted = await Template.delete('my-template')
* ```
*/
static async delete(nameOrId, options) {
const config = new ConnectionConfig(this.resolveOpts(options));
return deleteTemplate(new ApiClient(config), { templateID: nameOrId }, config.getSignal(void 0, options === null || options === void 0 ? void 0 : options.signal));
}
fromDebianImage(variant = "stable") {
return this.fromImage(`debian:${variant}`);
}
fromUbuntuImage(variant = "latest") {
return this.fromImage(`ubuntu:${variant}`);
}
fromFedoraImage(variant = "44") {
return this.fromImage(`fedora:${variant}`);
}
fromAlpineImage(variant = "3.24") {
return this.fromImage(`alpine:${variant}`);
}
fromArchImage(variant = "latest") {
return this.fromImage(`archlinux:${variant}`);
}
fromPythonImage(version = "3") {
return this.fromImage(`python:${version}`);
}
fromNodeImage(variant = "lts") {
return this.fromImage(`node:${variant}`);
}
fromBunImage(variant = "latest") {
return this.fromImage(`oven/bun:${variant}`);
}
fromBaseImage() {
return this.fromImage(this.defaultBaseImage);
}
fromImage(baseImage, credentials) {
if (credentials && (!credentials.username || !credentials.password)) throw new InvalidArgumentError("Both username and password are required when providing registry credentials", getCallerFrame());
this.baseImage = baseImage;
this.baseTemplate = void 0;
if (credentials) this.registryConfig = {
type: "registry",
username: credentials.username,
password: credentials.password
};
if (this.forceNextLayer) this.force = true;
this.collectStackTrace();
return this;
}
fromTemplate(template) {
this.baseTemplate = template;
this.baseImage = void 0;
if (this.forceNextLayer) this.force = true;
this.collectStackTrace();
return this;
}
fromDockerfile(dockerfileContentOrPath) {
const { baseImage } = parseDockerfile(dockerfileContentOrPath, this);
this.baseImage = baseImage;
this.baseTemplate = void 0;
if (this.forceNextLayer) this.force = true;
this.collectStackTrace();
return this;
}
fromAWSRegistry(image, credentials) {
this.baseImage = image;
this.baseTemplate = void 0;
this.registryConfig = {
type: "aws",
awsAccessKeyId: credentials.accessKeyId,
awsSecretAccessKey: credentials.secretAccessKey,
awsRegion: credentials.region
};
if (this.forceNextLayer) this.force = true;
this.collectStackTrace();
return this;
}
fromGCPRegistry(image, credentials) {
this.baseImage = image;
this.baseTemplate = void 0;
this.registryConfig = {
type: "gcp",
serviceAccountJson: readGCPServiceAccountJSON(this.fileContextPath.toString(), credentials.serviceAccountJSON)
};
if (this.forceNextLayer) this.force = true;
this.collectStackTrace();
return this;
}
copy(src, dest, options) {
if (runtime === "browser") throw new Error("Browser runtime is not supported for copy");
const srcs = Array.isArray(src) ? src : [src];
const stackTrace = getCallerFrame();
for (const src of srcs) {
var _options$user;
const srcString = src.toString();
validateRelativePath(srcString, stackTrace);
const args = [
srcString,
dest.toString(),
(_options$user = options === null || options === void 0 ? void 0 : options.user) !== null && _options$user !== void 0 ? _options$user : "",
(options === null || options === void 0 ? void 0 : options.mode) !== void 0 ? padOctal(options.mode) : ""
];
this.instructions.push({
type: "COPY",
args,
force: (options === null || options === void 0 ? void 0 : options.forceUpload) || this.forceNextLayer,
forceUpload: options === null || options === void 0 ? void 0 : options.forceUpload,
resolveSymlinks: options === null || options === void 0 ? void 0 : options.resolveSymlinks,
gzip: options === null || options === void 0 ? void 0 : options.gzip
});
this.collectStackTrace();
}
return this;
}
copyItems(items) {
if (runtime === "browser") throw new Error("Browser runtime is not supported for copyItems");
const stackTrace = getCallerFrame();
for (const item of items) try {
this.copy(item.src, item.dest, {
forceUpload: item.forceUpload,
user: item.user,
mode: item.mode,
resolveSymlinks: item.resolveSymlinks,
gzip: item.gzip
});
} catch (error) {
const copyError = error;
copyError.stack = stackTrace;
throw copyError;
}
return this;
}
remove(path, options) {
const paths = Array.isArray(path) ? path : [path];
const args = ["rm"];
if (options === null || options === void 0 ? void 0 : options.recursive) args.push("-r");
if (options === null || options === void 0 ? void 0 : options.force) args.push("-f");
args.push(...paths.map((p) => shellQuote(p.toString())));
return this.runCmd(args.join(" "), { user: options === null || options === void 0 ? void 0 : options.user });
}
rename(src, dest, options) {
const args = [
"mv",
shellQuote(src.toString()),
shellQuote(dest.toString())
];
if (options === null || options === void 0 ? void 0 : options.force) args.push("-f");
return this.runCmd(args.join(" "), { user: options === null || options === void 0 ? void 0 : options.user });
}
makeDir(path, options) {
const paths = Array.isArray(path) ? path : [path];
const args = ["mkdir", "-p"];
if ((options === null || options === void 0 ? void 0 : options.mode) !== void 0) args.push(`-m ${padOctal(options.mode)}`);
args.push(...paths.map((p) => shellQuote(p.toString())));
return this.runCmd(args.join(" "), { user: options === null || options === void 0 ? void 0 : options.user });
}
makeSymlink(src, dest, options) {
const args = ["ln", "-s"];
if (options === null || options === void 0 ? void 0 : options.force) args.push("-f");
args.push(shellQuote(src.toString()), shellQuote(dest.toString()));
return this.runCmd(args.join(" "), { user: options === null || options === void 0 ? void 0 : options.user });
}
runCmd(commandOrCommands, options) {
const args = [(Array.isArray(commandOrCommands) ? commandOrCommands : [commandOrCommands]).join(" && ")];
if (options === null || options === void 0 ? void 0 : options.user) args.push(options.user);
this.instructions.push({
type: "RUN",
args,
force: this.forceNextLayer
});
this.collectStackTrace();
return this;
}
setWorkdir(workdir) {
this.instructions.push({
type: "WORKDIR",
args: [workdir.toString()],
force: this.forceNextLayer
});
this.collectStackTrace();
return this;
}
setUser(user) {
this.instructions.push({
type: "USER",
args: [user],
force: this.forceNextLayer
});
this.collectStackTrace();
return this;
}
pipInstall(packages, options) {
var _options$g;
const g = (_options$g = options === null || options === void 0 ? void 0 : options.g) !== null && _options$g !== void 0 ? _options$g : true;
const args = ["pip", "install"];
const packageList = packages ? Array.isArray(packages) ? packages : [packages] : void 0;
if (g === false) args.push("--user");
if (packageList) args.push(...packageList);
else args.push(".");
return this.runCmd(args.join(" "), { user: g ? "root" : void 0 });
}
npmInstall(packages, options) {
const args = ["npm", "install"];
const packageList = packages ? Array.isArray(packages) ? packages : [packages] : void 0;
if (options === null || options === void 0 ? void 0 : options.g) args.push("-g");
if (options === null || options === void 0 ? void 0 : options.dev) args.push("--save-dev");
if (packageList) args.push(...packageList);
return this.runCmd(args.join(" "), { user: (options === null || options === void 0 ? void 0 : options.g) ? "root" : void 0 });
}
bunInstall(packages, options) {
const args = ["bun", "install"];
const packageList = packages ? Array.isArray(packages) ? packages : [packages] : void 0;
if (options === null || options === void 0 ? void 0 : options.g) args.push("-g");
if (options === null || options === void 0 ? void 0 : options.dev) args.push("--dev");
if (packageList) args.push(...packageList);
return this.runCmd(args.join(" "), { user: (options === null || options === void 0 ? void 0 : options.g) ? "root" : void 0 });
}
aptInstall(packages, options) {
const packageList = Array.isArray(packages) ? packages : [packages];
return this.runCmd(["apt-get update", `DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes apt-get install -y ${(options === null || options === void 0 ? void 0 : options.noInstallRecommends) ? "--no-install-recommends " : ""}${(options === null || options === void 0 ? void 0 : options.fixMissing) ? "--fix-missing " : ""}${packageList.join(" ")}`], { user: "root" });
}
addMcpServer(servers) {
if (this.baseTemplate !== "mcp-gateway") throw new BuildError("MCP servers can only be added to mcp-gateway template", getCallerFrame());
const serverList = Array.isArray(servers) ? servers : [servers];
return this.runCmd(`mcp-gateway pull ${serverList.join(" ")}`, { user: "root" });
}
gitClone(url, path, options) {
const args = [
"git",
"clone",
shellQuote(url)
];
if (options === null || options === void 0 ? void 0 : options.branch) {
args.push(`--branch ${shellQuote(options.branch)}`);
args.push("--single-branch");
}
if (options === null || options === void 0 ? void 0 : options.depth) args.push(`--depth ${options.depth}`);
if (path) args.push(shellQuote(path.toString()));
return this.runCmd(args.join(" "), { user: options === null || options === void 0 ? void 0 : options.user });
}
setStartCmd(startCommand, readyCommand) {
this.startCmd = startCommand;
if (readyCommand instanceof ReadyCmd) this.readyCmd = readyCommand.getCmd();
else this.readyCmd = readyCommand;
this.collectStackTrace();
return this;
}
setReadyCmd(readyCommand) {
if (readyCommand instanceof ReadyCmd) this.readyCmd = readyCommand.getCmd();
else this.readyCmd = readyCommand;
this.collectStackTrace();
return this;
}
setEnvs(envs) {
if (Object.keys(envs).length === 0) return this;
this.instructions.push({
type: "ENV",
args: Object.entries(envs).flatMap(([key, value]) => [key, value]),
force: this.forceNextLayer
});
this.collectStackTrace();
return this;
}
skipCache() {
this.forceNextLayer = true;
return this;
}
betaDevContainerPrebuild(devcontainerDirectory) {
if (this.baseTemplate !== "devcontainer") throw new BuildError("Devcontainers can only used in the devcontainer template", getCallerFrame());
return this.runCmd(`devcontainer build --workspace-folder ${shellQuote(devcontainerDirectory)}`, { user: "root" });
}
betaSetDevContainerStart(devcontainerDirectory) {
if (this.baseTemplate !== "devcontainer") throw new BuildError("Devcontainers can only used in the devcontainer template", getCallerFrame());
const dir = shellQuote(devcontainerDirectory);
return this.setStartCmd(`sudo devcontainer up --workspace-folder ${dir} && sudo /prepare-exec.sh ${dir} | sudo tee /devcontainer.sh > /dev/null && sudo chmod +x /devcontainer.sh && sudo touch /devcontainer.up`, waitForFile("/devcontainer.up"));
}
/**
* Collect the current stack trace for debugging purposes.
*
* The trace resolves to the first frame outside the SDK, so methods that
* delegate to other builder methods (e.g. `remove()` → `runCmd()`) collect
* the user's call site without any bookkeeping.
*
* @returns this for method chaining
*/
collectStackTrace() {
this.stackTraces.push(getCallerFrame());
return this;
}
/**
* Convert the template to JSON representation.
*
* @param computeHashes Whether to compute file hashes for COPY instructions
* @returns JSON string representation of the template
*/
async toJSON(computeHashes) {
var _this10 = this;
let instructions = _this10.instructions;
if (computeHashes) instructions = await _this10.instructionsWithHashes();
return JSON.stringify(_this10.serialize(instructions), void 0, 2);
}
/**
* Convert the template to Dockerfile format.
*
* Note: Only templates based on Docker images can be converted to Dockerfile.
* Templates based on other E2B templates cannot be converted because they
* may use features not available in standard Dockerfiles.
*
* @returns Dockerfile string representation
* @throws Error if template is based on another E2B template or has no base image
*/
toDockerfile() {
if (this.baseTemplate !== void 0) throw new Error("Cannot convert template built from another template to Dockerfile. Templates based on other templates can only be built using the E2B API.");
if (this.baseImage === void 0) throw new Error("No base image specified for template");
let dockerfile = `FROM ${this.baseImage}\n`;
for (const instruction of this.instructions) {
if (instruction.type === "RUN") {
dockerfile += `RUN ${instruction.args[0]}\n`;
continue;
}
if (instruction.type === "COPY") {
dockerfile += `COPY ${instruction.args[0]} ${instruction.args[1]}\n`;
continue;
}
if (instruction.type === "ENV") {
const values = [];
for (let i = 0; i < instruction.args.length; i += 2) values.push(`${instruction.args[i]}=${instruction.args[i + 1]}`);
dockerfile += `ENV ${values.join(" ")}\n`;
continue;
}
dockerfile += `${instruction.type} ${instruction.args.join(" ")}\n`;
}
if (this.startCmd) dockerfile += `ENTRYPOINT ${this.startCmd}\n`;
return dockerfile;
}
/**
* Internal implementation of the template build process.
*
* @param client API client for communicating with E2B backend
* @param name Template name in 'name' or 'name:tag' format
* @param tags Additional tags to assign to the build
* @param options Build configuration options
* @throws BuildError if the build fails
*/
async build(client, config, name, options) {
var _this11 = this;
var _options$onBuildLogs, _options$onBuildLogs2, _options$onBuildLogs5, _options$onBuildLogs6;
if (options.skipCache) _this11.force = true;
(_options$onBuildLogs = options.onBuildLogs) === null || _options$onBuildLogs === void 0 || _options$onBuildLogs.call(options, new LogEntry(/* @__PURE__ */ new Date(), "info", `Requesting build for template: ${name}${options.tags && options.tags.length > 0 ? ` with tags ${options.tags.join(", ")}` : ""}`));
const { templateID, buildID, tags: responseTags } = await requestBuild(client, {
name,
tags: options.tags,
cpuCount: options.cpuCount,
maxCpuCount: options.maxCpuCount,
memoryMB: options.memoryMB,
minFreeDiskMb: options.minFreeDiskMb
}, config.getSignal(void 0, options.signal));
(_options$onBuildLogs2 = options.onBuildLogs) === null || _options$onBuildLogs2 === void 0 || _options$onBuildLogs2.call(options, new LogEntry(/* @__PURE__ */ new Date(), "info", `Template created with ID: ${templateID}, Build ID: ${buildID}`));
const instructionsWithHashes = await _this11.instructionsWithHashes();
const uploadPromises = instructionsWithHashes.map(async (instruction, index) => {
var _instruction$filesHas;
if (instruction.type !== "COPY") return;
const src = instruction.args.length > 0 ? instruction.args[0] : null;
const filesHash = (_instruction$filesHas = instruction.filesHash) !== null && _instruction$filesHas !== void 0 ? _instruction$filesHas : null;
if (src === null || filesHash === null) throw new Error("Source path and files hash are required");
const forceUpload = instruction.forceUpload;
let stackTrace = void 0;
if (index + 1 >= 0 && index + 1 < _this11.stackTraces.length) stackTrace = _this11.stackTraces[index + 1];
const { present, url, headers } = await getFileUploadLink(client, {
templateID,
filesHash
}, stackTrace, config.getSignal(void 0, options.signal));
if (forceUpload && url != null || present === false && url != null) {
var _instruction$resolveS, _instruction$gzip, _options$onBuildLogs3;
await uploadFile({
fileName: src,
fileContextPath: _this11.fileContextPath.toString(),
url,
headers,
ignorePatterns: [...readDockerignore(_this11.fileContextPath.toString()), ..._this11.fileIgnorePatterns],
resolveSymlinks: (_instruction$resolveS = instruction.resolveSymlinks) !== null && _instruction$resolveS !== void 0 ? _instruction$resolveS : false,
gzip: (_instruction$gzip = instruction.gzip) !== null && _instruction$gzip !== void 0 ? _instruction$gzip : true
}, stackTrace, {
signal: options.signal,
requestTimeoutMs: options.requestTimeoutMs
});
(_options$onBuildLogs3 = options.onBuildLogs) === null || _options$onBuildLogs3 === void 0 || _options$onBuildLogs3.call(options, new LogEntry(/* @__PURE__ */ new Date(), "info", `Uploaded '${src}'`));
} else {
var _options$onBuildLogs4;
(_options$onBuildLogs4 = options.onBuildLogs) === null || _options$onBuildLogs4 === void 0 || _options$onBuildLogs4.call(options, new LogEntry(/* @__PURE__ */ new Date(), "info", `Skipping upload of '${src}', already cached`));
}
});
await Promise.all(uploadPromises);
(_options$onBuildLogs5 = options.onBuildLogs) === null || _options$onBuildLogs5 === void 0 || _options$onBuildLogs5.call(options, new LogEntry(/* @__PURE__ */ new Date(), "info", "All file uploads completed"));
(_options$onBuildLogs6 = options.onBuildLogs) === null || _options$onBuildLogs6 === void 0 || _options$onBuildLogs6.call(options, new LogEntry(/* @__PURE__ */ new Date(), "info", "Starting building..."));
await triggerBuild(client, {
templateID,
buildID,
template: _this11.serialize(instructionsWithHashes)
}, config.getSignal(void 0, options.signal));
return {
alias: name,
name,
tags: responseTags,
templateId: templateID,
buildId: buildID
};
}
/**
* Add file hashes to COPY instructions for cache invalidation.
*
* @returns Copy of instructions array with filesHash added to COPY instructions
*/
async instructionsWithHashes() {
var _this12 = this;
return Promise.all(_this12.instructions.map(async (instruction, index) => {
var _instruction$resolveS2;
if (instruction.type !== "COPY") return instruction;
const src = instruction.args.length > 0 ? instruction.args[0] : null;
const dest = instruction.args.length > 1 ? instruction.args[1] : null;
if (src === null || dest === null) throw new Error("Source path and destination path are required");
let stackTrace = void 0;
if (index + 1 >= 0 && index + 1 < _this12.stackTraces.length) stackTrace = _this12.stackTraces[index + 1];
return _objectSpread2(_objectSpread2({}, instruction), {}, { filesHash: await calculateFilesHash(src, dest, _this12.fileContextPath.toString(), [...runtime === "browser" ? [] : readDockerignore(_this12.fileContextPath.toString()), ..._this12.fileIgnorePatterns], (_instruction$resolveS2 = instruction.resolveSymlinks) !== null && _instruction$resolveS2 !== void 0 ? _instruction$resolveS2 : false, stackTrace) });
}));
}
/**
* Serialize the template to the API request format.
*
* @param steps Array of build instructions with file hashes
* @returns Template data formatted for the API
*/
serialize(steps) {
const templateData = {
startCmd: this.startCmd,
readyCmd: this.readyCmd,
steps,
force: this.force
};
if (this.baseImage !== void 0) templateData.fromImage = this.baseImage;
if (this.baseTemplate !== void 0) templateData.fromTemplate = this.baseTemplate;
if (this.registryConfig !== void 0) templateData.fromImageRegistry = this.registryConfig;
return templateData;
}
};
/**
* Builder and API entrypoint for E2B sandbox templates.
*
* `Template` is the {@link TemplateBase} class, wrapped so it can also be
* called as a factory returning a builder. The statics (`Template.build`,
* `Template.exists`, …) resolve their connection options off the class they are
* called on — so a subclass can bind its own defaults.
*
* @param options Optional builder options, e.g. the file context path used to
* resolve relative paths passed to `copy`
* @returns A template builder
*
* @example
* ```ts
* import { Template } from 'e2b'
*
* const template = Template()
* .fromPythonImage('3')
* .copy('requirements.txt', '/app/')
* .pipInstall()
*
* await Template.build(template, 'my-python-app:v1.0')
* ```
*/
const Template = callableTemplate(TemplateBase);
//#endregion
//#region src/client.ts
/**
* E2B client with an explicitly bound connection configuration.
*
* The resources exposed by the client ({@link E2B.Sandbox},
* {@link E2B.Volume}, {@link E2B.Template}, {@link E2B.Secret}) behave exactly
* like the top-level `Sandbox` / `Volume` / `Template` / `Secret` exports,
* except the options passed to the client are used as the defaults instead of
* the environment variables.
* Per-call options still take precedence over the client's options.
*
* Multiple clients are fully isolated from each other and from the top-level
* env-configured exports.
*
* @example
* ```ts
* import { E2B } from 'e2b'
*
* const client = new E2B({ apiKey: 'e2b_...', domain: 'e2b.dev' })
*
* const sandbox = await client.Sandbox.create()
* const volumes = await client.Volume.list()
* await client.Template.build(client.Template().fromPythonImage('3'), 'my-env')
* ```
*/
var E2B = class {
/**
* Create a new client with the connection options bound to it.
*
* @param opts connection options used as the defaults for every call made
* through this client's resource classes.
*/
constructor(opts) {
var _Class, _Class2, _Class3, _Class4;
_defineProperty(this, "Sandbox", void 0);
_defineProperty(this, "Volume", void 0);
_defineProperty(this, "Template", void 0);
_defineProperty(this, "Secret", void 0);
const boundOpts = _objectSpread2({}, opts !== null && opts !== void 0 ? opts : {});
delete boundOpts.signal;
this.Sandbox = (_Class = class extends Sandbox {}, _defineProperty(_Class, "boundOpts", boundOpts), _Class);
this.Volume = (_Class2 = class extends Volume {}, _defineProperty(_Class2, "boundOpts", boundOpts), _Class2);
this.Secret = (_Class3 = class extends Secret {}, _defineProperty(_Class3, "boundOpts", boundOpts), _Class3);
this.Template = callableTemplate((_Class4 = class extends TemplateBase {}, _defineProperty(_Class4, "boundOpts", boundOpts), _Class4));
}
};
//#endregion
//#region src/index.ts
var src_default = Sandbox;
//#endregion
exports.ALL_TRAFFIC = ALL_TRAFFIC;
exports.ApiClient = ApiClient;
exports.AuthenticationError = AuthenticationError;
exports.BuildError = BuildError;
exports.CommandExitError = CommandExitError;
exports.CommandInputHandle = CommandInputHandle;
exports.ConnectionConfig = ConnectionConfig;
exports.DockerfileSyntaxError = _e2b_dockerfile_utils.DockerfileSyntaxError;
exports.E2B = E2B;
exports.FileNotFoundError = FileNotFoundError;
exports.FileType = FileType;
exports.FileUploadError = FileUploadError;
exports.FilesystemEventType = FilesystemEventType;
exports.Git = Git;
exports.GitAuthError = GitAuthError;
exports.GitUpstreamError = GitUpstreamError;
exports.GuestUnresponsiveError = GuestUnresponsiveError;
exports.InvalidArgumentError = InvalidArgumentError;
exports.LogEntry = LogEntry;
exports.LogEntryEnd = LogEntryEnd;
exports.LogEntryStart = LogEntryStart;
exports.NotEnoughSpaceError = NotEnoughSpaceError;
exports.NotFoundError = NotFoundError;
exports.RateLimitError = RateLimitError;
exports.ReadyCmd = ReadyCmd;
exports.Sandbox = Sandbox;
exports.SandboxError = SandboxError;
exports.SandboxNotFoundError = SandboxNotFoundError;
exports.SandboxNotRunningError = SandboxNotRunningError;
exports.SandboxUnreachableError = SandboxUnreachableError;
exports.Secret = Secret;
exports.SecretError = SecretError;
exports.SecretNotFoundError = SecretNotFoundError;
exports.SecretPaginator = SecretPaginator;
exports.ServiceBusyError = ServiceBusyError;
exports.Template = Template;
exports.TemplateBase = TemplateBase;
exports.TemplateError = TemplateError;
exports.TemplatePaginator = TemplatePaginator;
exports.TimeoutError = TimeoutError;
exports.Volume = Volume;
exports.VolumeError = VolumeError;
exports.VolumeFileType = VolumeFileType;
exports.VolumeNotFoundError = VolumeNotFoundError;
exports.VolumePathNotFoundError = VolumePathNotFoundError;
exports.default = src_default;
exports.defaultBuildLogger = defaultBuildLogger;
exports.getSignature = getSignature;
exports.waitForFile = waitForFile;
exports.waitForPort = waitForPort;
exports.waitForProcess = waitForProcess;
exports.waitForTimeout = waitForTimeout;
exports.waitForURL = waitForURL;
//# sourceMappingURL=index.js.map