@hatchet-dev/sandbox 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,11 +1,29 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to `@hatchet-dev/sandbox`, the TypeScript SDK. The provider has its own changelog at the repository root.
3
+ All notable changes to `@hatchet-dev/sandbox`, the TypeScript SDK.
4
4
 
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Each release reads its notes from the matching section here.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.2] - 2026-11-03
10
+
11
+ ### Added
12
+
13
+ - `create({ provider })` picks the sandbox provider by name when a tenant has more than one. It is sent as the `provider` metadata tag, so providers that already read that tag need no change.
14
+ - The SDK works against any sandbox provider. On first use it checks whether the tenant serves the `hatchet-sandbox` task and uses that; otherwise it keeps using `brickie-sandbox`. Existing deployments need no change. A tenant should serve one of the two: turning on `hatchet-sandbox` while durable tasks that already called `brickie-sandbox` are in flight makes those tasks fail on resume with a non-determinism error, so drain them first.
15
+ - `HATCHET_SANDBOX_OTEL=false` turns the OpenTelemetry instrumentor off. `BRICKIE_OTEL=false` still works.
16
+
17
+ ### Changed
18
+
19
+ - The package declares `sideEffects: true` so bundlers keep the import that adds `hatchet.sandbox` to the client.
20
+ - Run metadata tags are now `hatchet.sandbox.fn`, `hatchet.sandbox.id`, `hatchet.sandbox.image` and `hatchet.sandbox.from` (were `brickie.fn`, `brickie.sandbox`, `brickie.image`, `brickie.from`). Update any dashboard or run filter that keys on the old names.
21
+
22
+ ### Fixed
23
+
24
+ - `files.write` of a staged file replayed in a durable task after its signed upload URL expired now asks the provider for a new URL instead of failing with `staging_failed`. For both `write` and `read`, that renewal is a plain task run rather than a durable child, so it does not shift the replayed child sequence.
25
+ - `files.write` with several files over 2 MiB converts every input first and then takes staged keys in file order, so a durable replay assigns the same key to the same file.
26
+
9
27
  ## [0.1.1] - 2026-10-29
10
28
 
11
29
  ### Added
package/README.md CHANGED
@@ -7,9 +7,11 @@
7
7
 
8
8
  </div>
9
9
 
10
- Run agent sandboxes from [Hatchet](https://hatchet.run) tasks.
10
+ Run sandboxes from [Hatchet](https://hatchet.run) tasks, on the sandbox vendor of your choice.
11
11
 
12
- A sandbox is a workspace with a shell. You run commands in it and read and write its files. Each call is a Hatchet task. Each call that changes files takes a snapshot. You can start a new sandbox from any snapshot, and you can come back to a sandbox later by its ID. If you use it inside a durable task, a crash picks up where it left off.
12
+ A sandbox is a workspace with a shell. You run commands in it and read and write its files. Each call is a Hatchet task and corresponds to a snapshot. You can start a new sandbox from any snapshot, and you can come back to a sandbox later by its ID. If you use it inside a durable task, a crash picks up where it left off.
13
+
14
+ Your code only talks to Hatchet. The sandbox itself runs on a provider you connect to your tenant.
13
15
 
14
16
  ## Install
15
17
 
@@ -54,39 +56,55 @@ const again = await hatchet.sandbox.connect('conv-42');
54
56
  await again.exec('cat haiku.txt');
55
57
  ```
56
58
 
57
- Your app only talks to Hatchet. A brickie provider, connected to the same tenant, runs the sandboxes. Set `HATCHET_CLIENT_TOKEN` like you would for any Hatchet client. That is all the app needs.
59
+ Set `HATCHET_CLIENT_TOKEN` like you would for any Hatchet client.
60
+
61
+ ## Providers
62
+
63
+ A provider is a sandbox vendor connected to your Hatchet tenant, configured once in the dashboard under `Sandboxes` with the vendor's API key and optional configuration values.
64
+
65
+ We currently support the following vendors:
66
+
67
+ - [Daytona](https://www.daytona.io)
68
+ - [E2B](https://e2b.dev)
69
+ - [Archil](https://archil.com)
70
+ - [Freestyle](https://freestyle.sh).
71
+
72
+ ```ts
73
+ const sandbox = await hatchet.sandbox.create({ provider: 'e2b' });
74
+ ```
58
75
 
59
76
  ## API
60
77
 
61
78
  ### `hatchet.sandbox`
62
79
 
63
- | Call | Returns | What it does |
64
- |---|---|---|
65
- | `create(opts?)` | `Sandbox` | Starts a sandbox. |
66
- | `connect(id, { template? })` | `Sandbox` | Picks up a sandbox from its latest snapshot. Starts it again if it was stopped. Throws if the ID does not exist. |
67
- | `list({ sandboxId?, metadata? })` | `SandboxInfo[]` | Finds sandboxes by ID or by metadata. All given pairs must match. |
68
- | `delete({ sandboxId?, metadata? })` | `string[]` | Deletes every matching sandbox with all of its snapshots and files, and returns their IDs. One filter is required. |
80
+ | Call | Returns | What it does |
81
+ | ----------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------ |
82
+ | `create(opts?)` | `Sandbox` | Starts a sandbox. |
83
+ | `connect(id, { template? })` | `Sandbox` | Picks up a sandbox from its latest snapshot. Starts it again if it was stopped. Throws if the ID does not exist. |
84
+ | `list({ sandboxId?, metadata? })` | `SandboxInfo[]` | Finds sandboxes by ID or by metadata. All given pairs must match. |
85
+ | `delete({ sandboxId?, metadata? })` | `string[]` | Deletes every matching sandbox with all of its snapshots and files, and returns their IDs. One filter is required. |
69
86
 
70
87
  Options for `create`:
71
88
 
72
- | Option | What it does |
73
- |---|---|
74
- | `id` | Pick your own sandbox ID. Lowercase letters, digits, and hyphens. It must not exist yet. |
75
- | `template` | The container image. Defaults to the provider's image. |
76
- | `from` | Start from this snapshot. Create two sandboxes from the same snapshot and you have a fork. |
77
- | `metadata` | Tags you can search for with `list`. |
89
+ | Option | What it does |
90
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
91
+ | `provider` | Which sandbox provider to create on, by its name. Needed when the tenant has several; a fork always uses its snapshot's. |
92
+ | `id` | Pick your own sandbox ID. Lowercase letters, digits, and hyphens. It must not exist yet. |
93
+ | `template` | The image or template, in the provider's own terms. Defaults to the provider's default. |
94
+ | `from` | Start from this snapshot. Create two sandboxes from the same snapshot and you have a fork. |
95
+ | `metadata` | Tags you can search for with `list`. |
78
96
  | `inheritParentMetadata` | With `from`, copy the tags of the sandbox that made the snapshot onto the new one. Tags given in `metadata` take precedence. |
79
- | `idleTimeoutMs` | Remove the sandbox after this long with no calls. The provider sets the maximum. |
97
+ | `idleTimeoutMs` | Stop the sandbox after this long with no calls. The provider sets the maximum. |
80
98
 
81
99
  ### `Sandbox`
82
100
 
83
- | Call | Returns | What it does |
84
- |---|---|---|
101
+ | Call | Returns | What it does |
102
+ | --------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
85
103
  | `exec(cmd, { timeoutMs? })` | `{ stdout, stderr, exitCode, snapshotId?, timings? }` | Runs `sh -c cmd` in the workspace and takes a snapshot. A non-zero exit code is a normal result, not an error. On timeout the exit code is 124. |
86
- | `kill()` | `void` | Stops the sandbox. The snapshots stay, so `connect` still works. |
87
- | `delete()` | `void` | Stops the sandbox and deletes every snapshot and file stored for it. `connect` fails afterwards. |
88
- | `sandboxId` | `string` | The ID to use with `connect` later. |
89
- | `files` | `Files` | The file API below. |
104
+ | `kill()` | `void` | Stops the sandbox. The snapshots stay, so `connect` still works. |
105
+ | `delete()` | `void` | Stops the sandbox and deletes every snapshot and file stored for it. `connect` fails afterwards. |
106
+ | `sandboxId` | `string` | The ID to use with `connect` later. |
107
+ | `files` | `Files` | The file API below. |
90
108
 
91
109
  Each result also has `timings` in milliseconds for `claim`, `restore`, `exec`, and `checkpoint`, and the `snapshotId` the call made.
92
110
 
@@ -94,19 +112,17 @@ Each result also has `timings` in milliseconds for `claim`, `restore`, `exec`, a
94
112
 
95
113
  Paths are relative to the workspace. A path that points outside it throws a `FilesystemError` with code `invalid_path`.
96
114
 
97
- | Call | What it does |
98
- |---|---|
99
- | `read(path, { format? })` | Reads a file. `format` is `'text'` (default), `'bytes'` for a `Uint8Array`, or `'blob'`. |
100
- | `write(path, data)` or `write([{ path, data }, ...])` | Writes one file or many. `data` can be a string, `ArrayBuffer`, `Uint8Array`, or `Blob`. |
101
- | `list(path?, { depth? })` | Lists entries with name, path, type, size, mode, permissions, and modified time. |
102
- | `exists(path)`, `getInfo(path)` | Checks a path or gets its info. |
103
- | `makeDir(path)`, `rename(from, to)`, `copy(from, to)`, `remove(path)` | The usual file operations. |
104
- | `download(url, path, { timeoutMs? })` | The provider fetches an https URL into the workspace. Private and loopback addresses are refused. |
105
- | `upload(path, url, { timeoutMs? })` | The provider sends a file to an https URL with HTTP PUT. Presigned URLs work well here. |
106
-
107
- The sandbox itself does not need network access for any of this.
115
+ | Call | What it does |
116
+ | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
117
+ | `read(path, { format? })` | Reads a file. `format` is `'text'` (default), `'bytes'` for a `Uint8Array`, or `'blob'`. |
118
+ | `write(path, data)` or `write([{ path, data }, ...])` | Writes one file or many. `data` can be a string, `ArrayBuffer`, `Uint8Array`, or `Blob`. |
119
+ | `list(path?, { depth? })` | Lists entries with name, path, type, size, mode, permissions, and modified time. |
120
+ | `exists(path)`, `getInfo(path)` | Checks a path or gets its info. |
121
+ | `makeDir(path)`, `rename(from, to)`, `copy(from, to)`, `remove(path)` | The usual file operations. |
122
+ | `download(url, path, { timeoutMs? })` | The provider fetches an https URL into the workspace. Private and loopback addresses are refused. |
123
+ | `upload(path, url, { timeoutMs? })` | The provider sends a file to an https URL with HTTP PUT. Presigned URLs work well here. |
108
124
 
109
- `read` and `write` carry up to 2 MiB through Hatchet. Bigger files go through the provider's object store (`--object-store` on the provider: S3, GCS, Azure Blob, or a local folder) and never pass through Hatchet. You call the same methods either way.
125
+ `read` and `write` carry up to 2 MiB through Hatchet. Providers with an object store stage bigger files there and never pass them through Hatchet; you call the same methods either way.
110
126
 
111
127
  ## Errors
112
128
 
@@ -117,17 +133,17 @@ The sandbox itself does not need network access for any of this.
117
133
 
118
134
  ## Telemetry
119
135
 
120
- On first use the SDK turns on the Hatchet OpenTelemetry instrumentor. If you have OpenTelemetry set up, sandbox calls show up as spans in your task's trace. Set `BRICKIE_OTEL=false` to turn this off.
136
+ On first use the SDK turns on the Hatchet OpenTelemetry instrumentor. If you have OpenTelemetry set up, sandbox calls show up as spans in your task's trace. Set `HATCHET_SANDBOX_OTEL=false` to turn this off.
121
137
 
122
138
  ## What you need
123
139
 
124
- - A Hatchet tenant. Hatchet Cloud, or a self-hosted Hatchet v0.110.0 or newer with gRPC operators turned on.
125
- - A brickie provider connected to that tenant. It is one binary, `brickie worker`. Get it from [github.com/hatchet-dev/brickie](https://github.com/hatchet-dev/brickie).
140
+ - A Hatchet Cloud tenant with sandboxes enabled.
141
+ - At least one sandbox provider connected to that tenant, configured under Sandboxes in the dashboard with your Daytona, E2B, Archil, or Freestyle API key.
126
142
 
127
143
  ## Changelog
128
144
 
129
- See [CHANGELOG.md](https://github.com/hatchet-dev/brickie/blob/main/sdks/typescript/CHANGELOG.md).
145
+ See [CHANGELOG.md](./CHANGELOG.md).
130
146
 
131
147
  ## License
132
148
 
133
- MIT. See [LICENSE](https://github.com/hatchet-dev/brickie/blob/main/LICENSE).
149
+ MIT. See [LICENSE](./LICENSE).
package/dist/index.cjs ADDED
@@ -0,0 +1,365 @@
1
+ 'use strict';
2
+
3
+ var v1 = require('@hatchet-dev/typescript-sdk/v1');
4
+ var fs = require('fs/promises');
5
+
6
+ function _interopNamespace(e) {
7
+ if (e && e.__esModule) return e;
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+
24
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
25
+
26
+ // src/sandbox.ts
27
+ var FilesystemError = class extends Error {
28
+ constructor(code, message) {
29
+ super(message);
30
+ this.code = code;
31
+ this.name = "FilesystemError";
32
+ }
33
+ code;
34
+ };
35
+ var toEntry = (e) => ({
36
+ ...e,
37
+ modifiedTime: e.modifiedTime ? new Date(e.modifiedTime) : void 0
38
+ });
39
+ var STAGE_OVER_BYTES = 2 << 20;
40
+ async function toBytes(data) {
41
+ if (typeof data === "string") return Buffer.from(data, "utf8");
42
+ if (data instanceof Blob) return Buffer.from(await data.arrayBuffer());
43
+ if (data instanceof Uint8Array) return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
44
+ return Buffer.from(data);
45
+ }
46
+ async function putStaged(url, bytes) {
47
+ if (url.startsWith("file://")) {
48
+ await fs__namespace.writeFile(decodeURIComponent(url.slice("file://".length)), bytes);
49
+ return;
50
+ }
51
+ const res = await fetch(url, {
52
+ method: "PUT",
53
+ body: new Uint8Array(bytes),
54
+ headers: { "Content-Type": "application/octet-stream" }
55
+ });
56
+ if (!res.ok)
57
+ throw new FilesystemError("staging_failed", `stage upload failed: HTTP ${res.status}`);
58
+ }
59
+ async function getStaged(url) {
60
+ if (url.startsWith("file://")) {
61
+ return fs__namespace.readFile(decodeURIComponent(url.slice("file://".length)));
62
+ }
63
+ const res = await fetch(url);
64
+ if (!res.ok)
65
+ throw new FilesystemError("staging_failed", `stage download failed: HTTP ${res.status}`);
66
+ return Buffer.from(await res.arrayBuffer());
67
+ }
68
+ var Files = class {
69
+ /** @internal */
70
+ constructor(turn) {
71
+ this.turn = turn;
72
+ }
73
+ turn;
74
+ async call(fn, input, fresh = false) {
75
+ const out = await this.turn(`$fs.${fn}`, input, fresh);
76
+ if (out.error) throw new FilesystemError(out.error.code, out.error.message);
77
+ return out;
78
+ }
79
+ async read(path, opts = {}) {
80
+ const out = await this.call("read", { path });
81
+ const bytes = out.url ? await this.readStaged(out.url, out.stagedKey) : Buffer.from(out.content ?? "", "base64");
82
+ if (opts.format === "bytes") return new Uint8Array(bytes);
83
+ if (opts.format === "blob") return new Blob([new Uint8Array(bytes)]);
84
+ return bytes.toString("utf8");
85
+ }
86
+ /** A replayed durable read may hold a URL that has expired; the provider signs the same staged file again. The renewal is a plain run, not a durable child, so the replayed child sequence stays aligned. */
87
+ async readStaged(url, stagedKey) {
88
+ try {
89
+ return await getStaged(url);
90
+ } catch (e) {
91
+ if (url.startsWith("file://") || !(e instanceof FilesystemError)) throw e;
92
+ const { getUrl } = await this.call("presign", { stagedKey }, true);
93
+ return getStaged(getUrl);
94
+ }
95
+ }
96
+ async write(pathOrFiles, data) {
97
+ if (typeof pathOrFiles !== "string") return this.writeFiles(pathOrFiles);
98
+ const [entry] = await this.writeFiles([{ path: pathOrFiles, data: data ?? "" }]);
99
+ return entry;
100
+ }
101
+ /** Writes several files in one task. */
102
+ async writeFiles(files) {
103
+ const bytes = await Promise.all(files.map((f) => toBytes(f.data)));
104
+ const encoded = [];
105
+ for (const [i, f] of files.entries()) {
106
+ const b = bytes[i];
107
+ if (b.length <= STAGE_OVER_BYTES) {
108
+ encoded.push({ path: f.path, content: b.toString("base64") });
109
+ continue;
110
+ }
111
+ const { stagedKey, putUrl } = await this.call("presign", {});
112
+ try {
113
+ await putStaged(putUrl, b);
114
+ encoded.push({ path: f.path, stagedKey });
115
+ } catch {
116
+ const again = await this.call("presign", {}, true);
117
+ await putStaged(again.putUrl, b);
118
+ encoded.push({ path: f.path, stagedKey: again.stagedKey });
119
+ }
120
+ }
121
+ const { entries = [] } = await this.call("write", { files: encoded });
122
+ return entries.map(toEntry);
123
+ }
124
+ /** Lists a directory. `depth` 1 (default) is the directory itself; higher values recurse. No snapshot is made. */
125
+ async list(path = ".", opts = {}) {
126
+ const { entries = [] } = await this.call("list", { path, depth: opts.depth ?? 1 });
127
+ return entries.map(toEntry);
128
+ }
129
+ /** True if the path exists. No snapshot is made. */
130
+ async exists(path) {
131
+ const { entry } = await this.call("stat", { path });
132
+ return entry != null;
133
+ }
134
+ /** Metadata of a file or directory. Throws `FilesystemError` with code `not_found` if absent. */
135
+ async getInfo(path) {
136
+ const { entry } = await this.call("stat", { path });
137
+ if (!entry) throw new FilesystemError("not_found", `${path} does not exist`);
138
+ return toEntry(entry);
139
+ }
140
+ /** Creates a directory and its parents. Returns false if it already existed. */
141
+ async makeDir(path) {
142
+ const { created } = await this.call("mkdir", { path });
143
+ return created === true;
144
+ }
145
+ /** Renames or moves a file or directory. */
146
+ async rename(oldPath, newPath) {
147
+ const { entry } = await this.call("rename", { path: oldPath, newPath });
148
+ return toEntry(entry);
149
+ }
150
+ /** Copies a file or directory tree. */
151
+ async copy(srcPath, dstPath) {
152
+ const { entry } = await this.call("copy", { path: srcPath, newPath: dstPath });
153
+ return toEntry(entry);
154
+ }
155
+ /** Deletes a file or directory tree. */
156
+ async remove(path) {
157
+ await this.call("remove", { path });
158
+ }
159
+ /** The provider fetches an https URL into the workspace. The sandbox needs no network. Private and loopback addresses are refused. */
160
+ async download(url, path, opts = {}) {
161
+ const { entry } = await this.call("download", {
162
+ url,
163
+ path,
164
+ ...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}
165
+ });
166
+ return toEntry(entry);
167
+ }
168
+ /** The provider sends a workspace file to an https URL with HTTP PUT, for example a presigned URL. */
169
+ async upload(path, url, opts = {}) {
170
+ await this.call("upload", {
171
+ path,
172
+ url,
173
+ ...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}
174
+ });
175
+ }
176
+ };
177
+
178
+ // src/otel.ts
179
+ var telemetryStarted = false;
180
+ async function setupTelemetry() {
181
+ if (telemetryStarted || process.env.HATCHET_SANDBOX_OTEL === "false" || process.env.BRICKIE_OTEL === "false") {
182
+ return;
183
+ }
184
+ telemetryStarted = true;
185
+ try {
186
+ const [{ registerInstrumentations }, { HatchetInstrumentor }] = await Promise.all([
187
+ import('@opentelemetry/instrumentation'),
188
+ import('@hatchet-dev/typescript-sdk/opentelemetry/index.js')
189
+ ]);
190
+ registerInstrumentations({ instrumentations: [new HatchetInstrumentor()] });
191
+ } catch (err) {
192
+ console.error(`sandbox: opentelemetry disabled: ${err.message}`);
193
+ }
194
+ }
195
+
196
+ // src/sandbox.ts
197
+ var TASK_NAMES = ["hatchet-sandbox", "brickie-sandbox"];
198
+ var taskNames = /* @__PURE__ */ new WeakMap();
199
+ async function registered(client, name) {
200
+ const namespace = client.config.namespace ?? "";
201
+ const full = name.startsWith(namespace) ? name : `${namespace}${name}`;
202
+ for (let offset = 0; ; ) {
203
+ const { rows = [] } = await client.workflows.list({ name: full, limit: 100, offset });
204
+ if (rows.some((w) => w.name === full)) return true;
205
+ if (rows.length === 0) return false;
206
+ offset += rows.length;
207
+ }
208
+ }
209
+ async function probe(client) {
210
+ for (const name of TASK_NAMES) {
211
+ if (await registered(client, name)) return name;
212
+ }
213
+ return void 0;
214
+ }
215
+ function resolveTaskName(client) {
216
+ let pending = taskNames.get(client);
217
+ if (!pending) {
218
+ pending = probe(client).then(
219
+ (name) => {
220
+ if (!name) taskNames.delete(client);
221
+ return name ?? TASK_NAMES[TASK_NAMES.length - 1];
222
+ },
223
+ (err) => {
224
+ taskNames.delete(client);
225
+ throw err;
226
+ }
227
+ );
228
+ taskNames.set(client, pending);
229
+ }
230
+ return pending;
231
+ }
232
+ function turnRunner(client) {
233
+ const resolved = resolveTaskName(client);
234
+ return async (input, opts) => {
235
+ const name = await resolved;
236
+ const additionalMetadata = {
237
+ "hatchet.sandbox.fn": input.fn,
238
+ ...input.sandboxId ? { "hatchet.sandbox.id": input.sandboxId } : {},
239
+ ...input.image ? { "hatchet.sandbox.image": input.image } : {},
240
+ ...input.snapshotId ? { "hatchet.sandbox.from": input.snapshotId } : {}
241
+ };
242
+ const result = opts?.fresh ? await (await client.admin.runWorkflow(name, input, {
243
+ additionalMetadata
244
+ })).result() : await client.workflow({ name }).run(input, { additionalMetadata });
245
+ return result[name] ?? result;
246
+ };
247
+ }
248
+ var Sandbox = class _Sandbox {
249
+ constructor(run, sandboxId, template) {
250
+ this.run = run;
251
+ this.sandboxId = sandboxId;
252
+ this.template = template;
253
+ this.files = new Files((fn, input, fresh) => this.turn(fn, input, fresh));
254
+ }
255
+ run;
256
+ sandboxId;
257
+ template;
258
+ /** File operations on the workspace. */
259
+ files;
260
+ /** Starts a new sandbox. Prefer `hatchet.sandbox.create`. */
261
+ static async create(client, opts = {}) {
262
+ await setupTelemetry();
263
+ const run = turnRunner(client);
264
+ const metadata = opts.provider ? { ...opts.metadata, provider: opts.provider } : opts.metadata;
265
+ const created = await run({
266
+ fn: "$create",
267
+ input: {},
268
+ ...opts.id ? { sandboxId: opts.id } : {},
269
+ ...opts.template ? { image: opts.template } : {},
270
+ ...opts.from ? { snapshotId: opts.from } : {},
271
+ ...metadata ? { metadata } : {},
272
+ ...opts.idleTimeoutMs ? { idleTtlMs: opts.idleTimeoutMs } : {},
273
+ ...opts.inheritParentMetadata ? { inheritMetadata: true } : {}
274
+ });
275
+ return new _Sandbox(run, created.sandboxId, opts.template);
276
+ }
277
+ /** Attaches to a sandbox by ID. Prefer `hatchet.sandbox.connect`. */
278
+ static async connect(client, sandboxId, opts = {}) {
279
+ await setupTelemetry();
280
+ const run = turnRunner(client);
281
+ await run({
282
+ fn: "$connect",
283
+ input: {},
284
+ sandboxId,
285
+ ...opts.template ? { image: opts.template } : {}
286
+ });
287
+ return new _Sandbox(run, sandboxId, opts.template);
288
+ }
289
+ /** Lists sandboxes on the provider. Prefer `hatchet.sandbox.list`. */
290
+ static async list(client, opts = {}) {
291
+ const result = await turnRunner(client)({
292
+ fn: "$list",
293
+ input: {
294
+ ...opts.sandboxId ? { sandboxId: opts.sandboxId } : {},
295
+ ...opts.metadata ? { metadata: opts.metadata } : {}
296
+ }
297
+ });
298
+ const raw = result.output.sandboxes ?? [];
299
+ return raw.map((s) => ({
300
+ ...s,
301
+ createdAt: new Date(s.createdAt),
302
+ lastUsed: s.lastUsed ? new Date(s.lastUsed) : void 0
303
+ }));
304
+ }
305
+ /** Deletes every sandbox matching the filter, with all their snapshots and files. Prefer `hatchet.sandbox.delete`. */
306
+ static async delete(client, opts) {
307
+ if (!opts.sandboxId && Object.keys(opts.metadata ?? {}).length === 0)
308
+ throw new Error("delete needs a sandboxId or a metadata filter");
309
+ const run = turnRunner(client);
310
+ const ids = [];
311
+ for (const { sandboxId } of await _Sandbox.list(client, opts)) {
312
+ await run({ fn: "$delete", input: {}, sandboxId });
313
+ ids.push(sandboxId);
314
+ }
315
+ return ids;
316
+ }
317
+ /** Runs a shell command with `sh -c` in the workspace and makes a snapshot. A timeout gives `exitCode` 124, not an error. */
318
+ async exec(cmd, opts = {}) {
319
+ return this.turn("$command", {
320
+ cmd,
321
+ ...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}
322
+ });
323
+ }
324
+ /** Removes the sandbox. Its snapshots stay; `connect` can bring it back. */
325
+ async kill() {
326
+ await this.turn("$destroy", {});
327
+ }
328
+ /** Removes the sandbox and deletes every snapshot and file the provider stored for it. `connect` fails afterwards. */
329
+ async delete() {
330
+ await this.turn("$delete", {});
331
+ }
332
+ async turn(fn, input, fresh = false) {
333
+ const result = await this.run(
334
+ {
335
+ sandboxId: this.sandboxId,
336
+ ...this.template ? { image: this.template } : {},
337
+ fn,
338
+ input
339
+ },
340
+ { fresh }
341
+ );
342
+ return {
343
+ ...result.output,
344
+ snapshotId: result.snapshotId,
345
+ timings: result.timings
346
+ };
347
+ }
348
+ };
349
+ Object.defineProperty(v1.HatchetClient.prototype, "sandbox", {
350
+ configurable: true,
351
+ get() {
352
+ return {
353
+ create: (opts) => Sandbox.create(this, opts),
354
+ connect: (sandboxId, opts) => Sandbox.connect(this, sandboxId, opts),
355
+ list: (opts) => Sandbox.list(this, opts),
356
+ delete: (opts) => Sandbox.delete(this, opts)
357
+ };
358
+ }
359
+ });
360
+
361
+ exports.Files = Files;
362
+ exports.FilesystemError = FilesystemError;
363
+ exports.Sandbox = Sandbox;
364
+ //# sourceMappingURL=index.cjs.map
365
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/files.ts","../src/otel.ts","../src/sandbox.ts"],"names":["fs","HatchetClient"],"mappings":"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","file":"index.cjs","sourcesContent":["import * as fs from 'node:fs/promises';\nimport type { JsonObject } from '@hatchet-dev/typescript-sdk/v1';\n\nexport type FileType = 'file' | 'dir';\n\nexport interface EntryInfo {\n name: string;\n path: string;\n type: FileType;\n size: number;\n mode: number;\n permissions: string;\n modifiedTime?: Date;\n}\n\nexport type FileData = string | ArrayBuffer | Uint8Array | Blob;\n\nexport interface WriteEntry {\n path: string;\n data: FileData;\n}\n\nexport interface ReadOpts {\n format?: 'text' | 'bytes' | 'blob';\n}\n\nexport interface TransferOpts {\n /** Give up after this long. */\n timeoutMs?: number;\n}\n\nexport interface ListOpts {\n depth?: number;\n}\n\nexport type FilesystemErrorCode =\n | 'staging_failed'\n | 'not_found'\n | 'invalid_path'\n | 'invalid_url'\n | 'invalid_content'\n | 'invalid'\n | 'is_a_directory'\n | 'not_a_directory'\n | 'too_large'\n | 'transfer_failed';\n\n/** Thrown for caller mistakes such as a missing file or a path outside the workspace. */\nexport class FilesystemError extends Error {\n constructor(\n readonly code: FilesystemErrorCode,\n message: string\n ) {\n super(message);\n this.name = 'FilesystemError';\n }\n}\n\ntype RawEntry = Omit<EntryInfo, 'modifiedTime'> & { modifiedTime?: string };\n\ninterface FsOutput extends JsonObject {\n content?: string;\n stagedKey?: string;\n url?: string;\n putUrl?: string;\n getUrl?: string;\n entry?: RawEntry | null;\n entries?: RawEntry[];\n created?: boolean;\n error?: { code: FilesystemErrorCode; message: string };\n}\n\n/** @internal */\nexport type FsTurn = (fn: string, input: JsonObject, fresh?: boolean) => Promise<FsOutput>;\n\nconst toEntry = (e: RawEntry): EntryInfo => ({\n ...e,\n modifiedTime: e.modifiedTime ? new Date(e.modifiedTime) : undefined,\n});\n\nconst STAGE_OVER_BYTES = 2 << 20;\n\nasync function toBytes(data: FileData): Promise<Buffer> {\n if (typeof data === 'string') return Buffer.from(data, 'utf8');\n if (data instanceof Blob) return Buffer.from(await data.arrayBuffer());\n if (data instanceof Uint8Array) return Buffer.from(data.buffer, data.byteOffset, data.byteLength);\n return Buffer.from(data);\n}\n\nasync function putStaged(url: string, bytes: Buffer): Promise<void> {\n if (url.startsWith('file://')) {\n await fs.writeFile(decodeURIComponent(url.slice('file://'.length)), bytes);\n return;\n }\n const res = await fetch(url, {\n method: 'PUT',\n body: new Uint8Array(bytes),\n headers: { 'Content-Type': 'application/octet-stream' },\n });\n if (!res.ok)\n throw new FilesystemError('staging_failed', `stage upload failed: HTTP ${res.status}`);\n}\n\nasync function getStaged(url: string): Promise<Buffer> {\n if (url.startsWith('file://')) {\n return fs.readFile(decodeURIComponent(url.slice('file://'.length)));\n }\n const res = await fetch(url);\n if (!res.ok)\n throw new FilesystemError('staging_failed', `stage download failed: HTTP ${res.status}`);\n return Buffer.from(await res.arrayBuffer());\n}\n/** File operations on a sandbox workspace. Paths are relative to the workspace. Each call is one Hatchet task. */\nexport class Files {\n /** @internal */\n constructor(private readonly turn: FsTurn) {}\n\n private async call(fn: string, input: JsonObject, fresh = false): Promise<FsOutput> {\n const out = await this.turn(`$fs.${fn}`, input, fresh);\n if (out.error) throw new FilesystemError(out.error.code, out.error.message);\n return out;\n }\n\n /** Reads a file as text (default), bytes, or a Blob. Files over the provider's `--max-file-bytes` (2 MiB) come through the provider's `--store` instead of Hatchet. */\n read(path: string, opts?: ReadOpts & { format?: 'text' }): Promise<string>;\n read(path: string, opts: ReadOpts & { format: 'bytes' }): Promise<Uint8Array>;\n read(path: string, opts: ReadOpts & { format: 'blob' }): Promise<Blob>;\n async read(path: string, opts: ReadOpts = {}): Promise<string | Uint8Array | Blob> {\n const out = await this.call('read', { path });\n const bytes = out.url\n ? await this.readStaged(out.url, out.stagedKey!)\n : Buffer.from(out.content ?? '', 'base64');\n if (opts.format === 'bytes') return new Uint8Array(bytes);\n if (opts.format === 'blob') return new Blob([new Uint8Array(bytes)]);\n return bytes.toString('utf8');\n }\n\n /** A replayed durable read may hold a URL that has expired; the provider signs the same staged file again. The renewal is a plain run, not a durable child, so the replayed child sequence stays aligned. */\n private async readStaged(url: string, stagedKey: string): Promise<Buffer> {\n try {\n return await getStaged(url);\n } catch (e) {\n if (url.startsWith('file://') || !(e instanceof FilesystemError)) throw e;\n const { getUrl } = await this.call('presign', { stagedKey }, true);\n return getStaged(getUrl!);\n }\n }\n\n /** Writes one file or several. Parent directories are created. Bytes are stored exactly. Files over 2 MiB go through the provider's `--store` instead of Hatchet. */\n write(path: string, data: FileData): Promise<EntryInfo>;\n write(files: WriteEntry[]): Promise<EntryInfo[]>;\n async write(\n pathOrFiles: string | WriteEntry[],\n data?: FileData\n ): Promise<EntryInfo | EntryInfo[]> {\n if (typeof pathOrFiles !== 'string') return this.writeFiles(pathOrFiles);\n const [entry] = await this.writeFiles([{ path: pathOrFiles, data: data ?? '' }]);\n return entry;\n }\n\n /** Writes several files in one task. */\n async writeFiles(files: WriteEntry[]): Promise<EntryInfo[]> {\n const bytes = await Promise.all(files.map((f) => toBytes(f.data)));\n const encoded: Array<{ path: string; content?: string; stagedKey?: string }> = [];\n for (const [i, f] of files.entries()) {\n const b = bytes[i];\n if (b.length <= STAGE_OVER_BYTES) {\n encoded.push({ path: f.path, content: b.toString('base64') });\n continue;\n }\n const { stagedKey, putUrl } = await this.call('presign', {});\n try {\n await putStaged(putUrl!, b);\n encoded.push({ path: f.path, stagedKey: stagedKey! });\n } catch {\n const again = await this.call('presign', {}, true);\n await putStaged(again.putUrl!, b);\n encoded.push({ path: f.path, stagedKey: again.stagedKey! });\n }\n }\n const { entries = [] } = await this.call('write', { files: encoded });\n return entries.map(toEntry);\n }\n\n /** Lists a directory. `depth` 1 (default) is the directory itself; higher values recurse. No snapshot is made. */\n async list(path = '.', opts: ListOpts = {}): Promise<EntryInfo[]> {\n const { entries = [] } = await this.call('list', { path, depth: opts.depth ?? 1 });\n return entries.map(toEntry);\n }\n\n /** True if the path exists. No snapshot is made. */\n async exists(path: string): Promise<boolean> {\n const { entry } = await this.call('stat', { path });\n return entry != null;\n }\n\n /** Metadata of a file or directory. Throws `FilesystemError` with code `not_found` if absent. */\n async getInfo(path: string): Promise<EntryInfo> {\n const { entry } = await this.call('stat', { path });\n if (!entry) throw new FilesystemError('not_found', `${path} does not exist`);\n return toEntry(entry);\n }\n\n /** Creates a directory and its parents. Returns false if it already existed. */\n async makeDir(path: string): Promise<boolean> {\n const { created } = await this.call('mkdir', { path });\n return created === true;\n }\n\n /** Renames or moves a file or directory. */\n async rename(oldPath: string, newPath: string): Promise<EntryInfo> {\n const { entry } = await this.call('rename', { path: oldPath, newPath });\n return toEntry(entry!);\n }\n\n /** Copies a file or directory tree. */\n async copy(srcPath: string, dstPath: string): Promise<EntryInfo> {\n const { entry } = await this.call('copy', { path: srcPath, newPath: dstPath });\n return toEntry(entry!);\n }\n\n /** Deletes a file or directory tree. */\n async remove(path: string): Promise<void> {\n await this.call('remove', { path });\n }\n /** The provider fetches an https URL into the workspace. The sandbox needs no network. Private and loopback addresses are refused. */\n async download(url: string, path: string, opts: TransferOpts = {}): Promise<EntryInfo> {\n const { entry } = await this.call('download', {\n url,\n path,\n ...(opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}),\n });\n return toEntry(entry!);\n }\n /** The provider sends a workspace file to an https URL with HTTP PUT, for example a presigned URL. */\n async upload(path: string, url: string, opts: TransferOpts = {}): Promise<void> {\n await this.call('upload', {\n path,\n url,\n ...(opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}),\n });\n }\n}\n","let telemetryStarted = false;\n\nexport async function setupTelemetry(): Promise<void> {\n if (\n telemetryStarted ||\n process.env.HATCHET_SANDBOX_OTEL === 'false' ||\n process.env.BRICKIE_OTEL === 'false'\n ) {\n return;\n }\n telemetryStarted = true;\n try {\n const [{ registerInstrumentations }, { HatchetInstrumentor }] = await Promise.all([\n import('@opentelemetry/instrumentation'),\n import('@hatchet-dev/typescript-sdk/opentelemetry/index.js'),\n ]);\n registerInstrumentations({ instrumentations: [new HatchetInstrumentor()] });\n } catch (err) {\n console.error(`sandbox: opentelemetry disabled: ${(err as Error).message}`);\n }\n}\n","import { HatchetClient, JsonObject } from '@hatchet-dev/typescript-sdk/v1';\nimport { Files } from './files.js';\nimport { setupTelemetry } from './otel.js';\n\nconst TASK_NAMES = ['hatchet-sandbox', 'brickie-sandbox'];\nconst taskNames = new WeakMap<HatchetClient, Promise<string>>();\n\nasync function registered(client: HatchetClient, name: string): Promise<boolean> {\n const namespace = client.config.namespace ?? '';\n const full = name.startsWith(namespace) ? name : `${namespace}${name}`;\n for (let offset = 0; ;) {\n const { rows = [] } = await client.workflows.list({ name: full, limit: 100, offset });\n if (rows.some((w) => w.name === full)) return true;\n if (rows.length === 0) return false;\n offset += rows.length;\n }\n}\n\nasync function probe(client: HatchetClient): Promise<string | undefined> {\n for (const name of TASK_NAMES) {\n if (await registered(client, name)) return name;\n }\n return undefined;\n}\n\nfunction resolveTaskName(client: HatchetClient): Promise<string> {\n let pending = taskNames.get(client);\n if (!pending) {\n pending = probe(client).then(\n (name) => {\n if (!name) taskNames.delete(client);\n return name ?? TASK_NAMES[TASK_NAMES.length - 1];\n },\n (err) => {\n taskNames.delete(client);\n throw err;\n }\n );\n taskNames.set(client, pending);\n }\n return pending;\n}\n\ninterface TurnInput extends JsonObject {\n sandboxId?: string;\n image?: string;\n fn: string;\n input: JsonObject;\n snapshotId?: string;\n metadata?: Record<string, string>;\n idleTtlMs?: number;\n inheritMetadata?: boolean;\n}\n\ninterface TurnResult extends JsonObject {\n output: JsonObject;\n sandboxId: string;\n snapshotId?: string;\n timings?: Record<string, number>;\n}\n\nexport interface Timings {\n /** Snapshot made after this turn. Absent for turns that do not change the workspace. */\n snapshotId?: string;\n /** Milliseconds per phase: `claim`, `restore`, `exec`, `checkpoint`. */\n timings?: Record<string, number>;\n}\n\nexport interface CommandResult extends Timings {\n stdout: string;\n stderr: string;\n /** A non-zero code is a normal result, not an error. */\n exitCode: number;\n}\n\nexport interface SandboxOpts {\n /** Sandbox ID to use instead of a generated one. Lowercase letters, digits, and hyphens; must be new on the provider. */\n id?: string;\n /** Container image. Defaults to the provider's image. */\n template?: string;\n /** Snapshot to start from. Two sandboxes with the same `from` are a fork. */\n from?: string;\n /** Key-value tags, used to find the sandbox again with {@link SandboxNamespace.list}. */\n metadata?: Record<string, string>;\n /** Milliseconds without a turn before the provider removes the sandbox. Capped by the provider. */\n idleTimeoutMs?: number;\n /** With `from`, start from the metadata of the sandbox that made that snapshot. Pairs given in `metadata` take precedence over inherited ones. */\n inheritParentMetadata?: boolean;\n /** Name of the sandbox provider to create on. Required when the tenant has more than one; with `from`, it must be the provider that holds the snapshot. */\n provider?: string;\n}\n\nexport interface ExecOpts {\n /** Kill the command after this long. The result then has `exitCode` 124. */\n timeoutMs?: number;\n}\n\nexport interface ConnectOpts {\n /** Image to use if the sandbox has to be started again. Defaults to the provider's image. */\n template?: string;\n}\n\nexport interface SandboxListOpts {\n /** Only the sandbox with this ID. */\n sandboxId?: string;\n /** Only sandboxes whose metadata contains every one of these pairs. */\n metadata?: Record<string, string>;\n}\n\nexport interface SandboxInfo {\n sandboxId: string;\n metadata?: Record<string, string>;\n /** True while the sandbox is running. A sandbox that is not live is started again by `connect`. */\n live: boolean;\n latestSnapshot?: string;\n createdAt: Date;\n lastUsed?: Date;\n}\n\ntype RunTurn = (input: TurnInput, opts?: { fresh?: boolean }) => Promise<TurnResult>;\n\nfunction turnRunner(client: HatchetClient): RunTurn {\n const resolved = resolveTaskName(client);\n return async (input, opts) => {\n const name = await resolved;\n const additionalMetadata = {\n 'hatchet.sandbox.fn': input.fn,\n ...(input.sandboxId ? { 'hatchet.sandbox.id': input.sandboxId } : {}),\n ...(input.image ? { 'hatchet.sandbox.image': input.image } : {}),\n ...(input.snapshotId ? { 'hatchet.sandbox.from': input.snapshotId } : {}),\n };\n const result = opts?.fresh\n ? await (\n await client.admin.runWorkflow<TurnInput, Record<string, TurnResult>>(name, input, {\n additionalMetadata,\n })\n ).result()\n : await client\n .workflow<TurnInput, Record<string, TurnResult>>({ name })\n .run(input, { additionalMetadata });\n return result[name] ?? (result as unknown as TurnResult);\n };\n}\n\n/** One sandbox. Every method is one Hatchet task on the tenant's sandbox provider. */\nexport class Sandbox {\n /** File operations on the workspace. */\n readonly files: Files;\n\n private constructor(\n private readonly run: RunTurn,\n readonly sandboxId: string,\n private readonly template?: string\n ) {\n this.files = new Files((fn, input, fresh) => this.turn(fn, input, fresh));\n }\n\n /** Starts a new sandbox. Prefer `hatchet.sandbox.create`. */\n static async create(client: HatchetClient, opts: SandboxOpts = {}): Promise<Sandbox> {\n await setupTelemetry();\n const run = turnRunner(client);\n const metadata = opts.provider ? { ...opts.metadata, provider: opts.provider } : opts.metadata;\n const created = await run({\n fn: '$create',\n input: {},\n ...(opts.id ? { sandboxId: opts.id } : {}),\n ...(opts.template ? { image: opts.template } : {}),\n ...(opts.from ? { snapshotId: opts.from } : {}),\n ...(metadata ? { metadata } : {}),\n ...(opts.idleTimeoutMs ? { idleTtlMs: opts.idleTimeoutMs } : {}),\n ...(opts.inheritParentMetadata ? { inheritMetadata: true } : {}),\n });\n return new Sandbox(run, created.sandboxId, opts.template);\n }\n\n /** Attaches to a sandbox by ID. Prefer `hatchet.sandbox.connect`. */\n static async connect(\n client: HatchetClient,\n sandboxId: string,\n opts: ConnectOpts = {}\n ): Promise<Sandbox> {\n await setupTelemetry();\n const run = turnRunner(client);\n await run({\n fn: '$connect',\n input: {},\n sandboxId,\n ...(opts.template ? { image: opts.template } : {}),\n });\n return new Sandbox(run, sandboxId, opts.template);\n }\n\n /** Lists sandboxes on the provider. Prefer `hatchet.sandbox.list`. */\n static async list(client: HatchetClient, opts: SandboxListOpts = {}): Promise<SandboxInfo[]> {\n const result = await turnRunner(client)({\n fn: '$list',\n input: {\n ...(opts.sandboxId ? { sandboxId: opts.sandboxId } : {}),\n ...(opts.metadata ? { metadata: opts.metadata } : {}),\n },\n });\n const raw = (result.output.sandboxes ?? []) as Array<\n Omit<SandboxInfo, 'createdAt' | 'lastUsed'> & { createdAt: string; lastUsed?: string }\n >;\n return raw.map((s) => ({\n ...s,\n createdAt: new Date(s.createdAt),\n lastUsed: s.lastUsed ? new Date(s.lastUsed) : undefined,\n }));\n }\n\n /** Deletes every sandbox matching the filter, with all their snapshots and files. Prefer `hatchet.sandbox.delete`. */\n static async delete(client: HatchetClient, opts: SandboxListOpts): Promise<string[]> {\n if (!opts.sandboxId && Object.keys(opts.metadata ?? {}).length === 0)\n throw new Error('delete needs a sandboxId or a metadata filter');\n const run = turnRunner(client);\n const ids: string[] = [];\n for (const { sandboxId } of await Sandbox.list(client, opts)) {\n await run({ fn: '$delete', input: {}, sandboxId });\n ids.push(sandboxId);\n }\n return ids;\n }\n\n /** Runs a shell command with `sh -c` in the workspace and makes a snapshot. A timeout gives `exitCode` 124, not an error. */\n async exec(cmd: string, opts: ExecOpts = {}): Promise<CommandResult> {\n return this.turn<CommandResult>('$command', {\n cmd,\n ...(opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}),\n });\n }\n\n /** Removes the sandbox. Its snapshots stay; `connect` can bring it back. */\n async kill(): Promise<void> {\n await this.turn('$destroy', {});\n }\n\n /** Removes the sandbox and deletes every snapshot and file the provider stored for it. `connect` fails afterwards. */\n async delete(): Promise<void> {\n await this.turn('$delete', {});\n }\n\n private async turn<T = JsonObject>(fn: string, input: JsonObject, fresh = false): Promise<T> {\n const result = await this.run(\n {\n sandboxId: this.sandboxId,\n ...(this.template ? { image: this.template } : {}),\n fn,\n input,\n },\n { fresh }\n );\n return {\n ...result.output,\n snapshotId: result.snapshotId,\n timings: result.timings,\n } as unknown as T;\n }\n}\n\n/** `hatchet.sandbox`, added to `HatchetClient` by importing `@hatchet-dev/sandbox`. */\nexport interface SandboxNamespace {\n /** Starts a new sandbox. Inside a durable task this is a durable step. */\n create(opts?: SandboxOpts): Promise<Sandbox>;\n /** Attaches to a sandbox by ID. If it is not running, the provider starts it from its latest snapshot. */\n connect(sandboxId: string, opts?: ConnectOpts): Promise<Sandbox>;\n /** Lists sandboxes the provider knows, filtered by ID or metadata. */\n list(opts?: SandboxListOpts): Promise<SandboxInfo[]>;\n /** Deletes the sandboxes matching the filter, with every snapshot and file stored for them. Returns their IDs. */\n delete(opts: SandboxListOpts): Promise<string[]>;\n}\n\ndeclare module '@hatchet-dev/typescript-sdk/v1' {\n interface HatchetClient {\n readonly sandbox: SandboxNamespace;\n }\n}\n\nObject.defineProperty(HatchetClient.prototype, 'sandbox', {\n configurable: true,\n get(this: HatchetClient): SandboxNamespace {\n return {\n create: (opts) => Sandbox.create(this, opts),\n connect: (sandboxId, opts) => Sandbox.connect(this, sandboxId, opts),\n list: (opts) => Sandbox.list(this, opts),\n delete: (opts) => Sandbox.delete(this, opts),\n };\n },\n});\n"]}