agentbox-sdk 0.1.0 → 0.1.3

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.
@@ -1,15 +1,22 @@
1
1
  import {
2
2
  AsyncQueue,
3
3
  UnsupportedProviderError,
4
+ collectAllAgentReservedPorts,
5
+ debugSandbox,
4
6
  pipeReadableStream,
5
7
  readNodeStream,
6
8
  readStreamAsText,
7
- sleep
8
- } from "./chunk-HMBWQSVN.js";
9
+ sleep,
10
+ suppressUnhandledRejection,
11
+ time
12
+ } from "./chunk-INMA52FV.js";
9
13
  import {
10
14
  shellQuote,
11
15
  toShellCommand
12
- } from "./chunk-JFDP556Q.js";
16
+ } from "./chunk-NSJM57Z4.js";
17
+ import {
18
+ SandboxProvider
19
+ } from "./chunk-GOFJNFAD.js";
13
20
 
14
21
  // src/sandboxes/git.ts
15
22
  function encodeExtraHeader(name, value) {
@@ -55,30 +62,122 @@ var SandboxAdapter = class {
55
62
  baseEnv;
56
63
  provisioned = false;
57
64
  provisioning;
65
+ /**
66
+ * Whether `provision()` warm-attached to a pre-existing tagged sandbox
67
+ * (true) or had to create a fresh one (false). Set by adapter
68
+ * `provision()` implementations. Stays `false` until `findOrProvision()`
69
+ * has resolved.
70
+ */
71
+ wasFoundFlag = false;
58
72
  constructor(options) {
59
73
  this.options = options;
60
74
  this.baseEnv = { ...options.env ?? {} };
61
75
  }
62
- async ensureProvisioned() {
76
+ async uploadFile(_content, _targetPath) {
77
+ void _content;
78
+ void _targetPath;
79
+ throw new Error(
80
+ `uploadFile is not supported by the ${this.provider} provider.`
81
+ );
82
+ }
83
+ async downloadFile(_sourcePath) {
84
+ void _sourcePath;
85
+ throw new Error(
86
+ `downloadFile is not supported by the ${this.provider} provider.`
87
+ );
88
+ }
89
+ /**
90
+ * Upload a tarball of files into the sandbox and execute a command in
91
+ * the same round-trip. Used by setup paths that would otherwise need one
92
+ * sandbox RPC per file plus another to run the install script — Modal-
93
+ * style providers pay ~700ms per RPC, so collapsing N+1 calls into one
94
+ * is the single biggest win on cold setup.
95
+ *
96
+ * Default implementation falls back to `uploadFile` per entry + a final
97
+ * `run`. Providers that support stdin streaming (Modal) override this to
98
+ * do the upload + extract + exec in a single sandbox `exec` call.
99
+ */
100
+ async uploadAndRun(files, command, options) {
101
+ this.requireProvisioned();
102
+ for (const entry of files) {
103
+ const content = typeof entry.content === "string" ? Buffer.from(entry.content, "utf8") : entry.content;
104
+ await this.uploadFile(content, entry.path);
105
+ }
106
+ if (files.length > 0) {
107
+ const chmodCmd = files.filter((entry) => entry.mode && (entry.mode & 73) !== 0).map(
108
+ (entry) => `chmod ${entry.mode.toString(8)} ${shellQuote(entry.path)}`
109
+ );
110
+ if (chmodCmd.length > 0) {
111
+ await this.run(chmodCmd.join(" && "), options);
112
+ }
113
+ }
114
+ return this.run(command, options);
115
+ }
116
+ /**
117
+ * Public hook that callers must invoke before they touch the sandbox
118
+ * (running commands, cloning repos, uploading files, opening preview
119
+ * links, …). It either attaches to an existing tagged sandbox or creates
120
+ * a new one. The result is cached so repeated calls are cheap.
121
+ *
122
+ * Provisioning is no longer triggered implicitly by `run`, `runAsync`,
123
+ * `gitClone`, `uploadAndRun`, etc. Those methods now throw a clear error
124
+ * when the adapter has not been provisioned yet, which makes the
125
+ * lifecycle explicit and gives callers control over when the
126
+ * (potentially slow) sandbox attach / create happens.
127
+ */
128
+ async findOrProvision() {
63
129
  if (this.provisioned) {
64
130
  return;
65
131
  }
66
132
  if (!this.provisioning) {
67
- this.provisioning = (async () => {
68
- await this.provision();
69
- this.provisioned = true;
70
- })().finally(() => {
133
+ this.provisioning = time(
134
+ debugSandbox,
135
+ `provision [${this.provider}] (find-or-create)`,
136
+ async () => {
137
+ await this.provision();
138
+ this.provisioned = true;
139
+ }
140
+ ).finally(() => {
71
141
  this.provisioning = void 0;
72
142
  });
73
143
  }
74
144
  await this.provisioning;
75
145
  }
146
+ /**
147
+ * Throw a consistent error when a method that needs a provisioned
148
+ * sandbox is called before `findOrProvision()`. Provider adapters call
149
+ * this at the top of `run`, `runAsync`, `uploadFile`, etc.
150
+ */
151
+ requireProvisioned() {
152
+ if (!this.provisioned) {
153
+ throw new Error(
154
+ `Sandbox (${this.provider}) is not provisioned. Call \`sandbox.findOrProvision()\` once before running commands, cloning repos, or uploading files.`
155
+ );
156
+ }
157
+ }
76
158
  get tags() {
77
159
  return { ...this.options.tags ?? {} };
78
160
  }
79
161
  get workingDir() {
80
162
  return this.options.workingDir ?? "/workspace";
81
163
  }
164
+ /**
165
+ * Whether `findOrProvision()` warm-attached to a pre-existing tagged
166
+ * sandbox (`true`) or created a fresh one (`false`). Useful to skip
167
+ * idempotent setup that the previous run already performed (e.g.
168
+ * `agent.setup()`). Always `false` before `findOrProvision()` resolves.
169
+ */
170
+ get wasFound() {
171
+ return this.wasFoundFlag;
172
+ }
173
+ /**
174
+ * Headers that callers should attach to HTTP / WebSocket requests they make
175
+ * against this sandbox's preview URL. Default is empty; providers like
176
+ * Vercel override this to inject Deployment Protection bypass tokens.
177
+ */
178
+ get previewHeaders() {
179
+ return {};
180
+ }
82
181
  getMergedEnv(extra) {
83
182
  return {
84
183
  ...this.baseEnv,
@@ -93,7 +192,7 @@ var SandboxAdapter = class {
93
192
  Object.assign(this.secrets, values);
94
193
  }
95
194
  async gitClone(options) {
96
- await this.ensureProvisioned();
195
+ this.requireProvisioned();
97
196
  return this.run(buildGitCloneCommand(options), {
98
197
  cwd: this.workingDir,
99
198
  env: this.getMergedEnv()
@@ -127,7 +226,7 @@ var DaytonaSandboxAdapter = class extends SandboxAdapter {
127
226
  });
128
227
  }
129
228
  get provider() {
130
- return "daytona";
229
+ return SandboxProvider.Daytona;
131
230
  }
132
231
  get raw() {
133
232
  return {
@@ -143,6 +242,7 @@ var DaytonaSandboxAdapter = class extends SandboxAdapter {
143
242
  if (existing) {
144
243
  this.sandbox = existing;
145
244
  await existing.start();
245
+ this.wasFoundFlag = true;
146
246
  return;
147
247
  }
148
248
  const labels = this.getLabels();
@@ -171,6 +271,7 @@ var DaytonaSandboxAdapter = class extends SandboxAdapter {
171
271
  autoDeleteInterval
172
272
  };
173
273
  const sandbox = await this.client.create({
274
+ ...this.options.provider?.createParams,
174
275
  ...createBase,
175
276
  snapshot: image
176
277
  });
@@ -178,7 +279,7 @@ var DaytonaSandboxAdapter = class extends SandboxAdapter {
178
279
  this.sandbox = sandbox;
179
280
  }
180
281
  async run(command, options) {
181
- await this.ensureProvisioned();
282
+ this.requireProvisioned();
182
283
  const sandbox = this.requireSandbox();
183
284
  const result = await sandbox.process.executeCommand(
184
285
  toShellCommand(command),
@@ -196,7 +297,7 @@ var DaytonaSandboxAdapter = class extends SandboxAdapter {
196
297
  };
197
298
  }
198
299
  async runAsync(command, options) {
199
- await this.ensureProvisioned();
300
+ this.requireProvisioned();
200
301
  const sandbox = this.requireSandbox();
201
302
  const sessionId = `agentbox-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
202
303
  await sandbox.process.createSession(sessionId);
@@ -288,9 +389,17 @@ var DaytonaSandboxAdapter = class extends SandboxAdapter {
288
389
  queue.fail(error);
289
390
  throw error;
290
391
  });
392
+ suppressUnhandledRejection(completion);
291
393
  return {
292
394
  id: commandId,
293
395
  raw: { sessionId, commandId },
396
+ write: async (input) => {
397
+ await sandbox.process.sendSessionCommandInput(
398
+ sessionId,
399
+ commandId,
400
+ input
401
+ );
402
+ },
294
403
  wait: () => completion,
295
404
  kill: async () => {
296
405
  killed = true;
@@ -331,15 +440,26 @@ var DaytonaSandboxAdapter = class extends SandboxAdapter {
331
440
  this.sandbox = void 0;
332
441
  }
333
442
  async openPort(port) {
334
- await this.ensureProvisioned();
443
+ this.requireProvisioned();
335
444
  await this.requireSandbox().getPreviewLink(port);
336
445
  }
337
446
  async getPreviewLink(port) {
338
- await this.ensureProvisioned();
447
+ this.requireProvisioned();
339
448
  const sandbox = this.requireSandbox();
340
449
  const preview = await sandbox.getPreviewLink(port);
341
450
  return preview.url;
342
451
  }
452
+ async uploadFile(content, targetPath) {
453
+ this.requireProvisioned();
454
+ const sandbox = this.requireSandbox();
455
+ const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8");
456
+ await sandbox.fs.uploadFile(buffer, targetPath);
457
+ }
458
+ async downloadFile(sourcePath) {
459
+ this.requireProvisioned();
460
+ const sandbox = this.requireSandbox();
461
+ return sandbox.fs.downloadFile(sourcePath);
462
+ }
343
463
  getLabels() {
344
464
  return {
345
465
  "agentbox.provider": this.provider,
@@ -382,7 +502,7 @@ async function loadE2bModule() {
382
502
  var E2bSandboxAdapter = class extends SandboxAdapter {
383
503
  sandbox;
384
504
  get provider() {
385
- return "e2b";
505
+ return SandboxProvider.E2B;
386
506
  }
387
507
  get raw() {
388
508
  return {
@@ -397,6 +517,7 @@ var E2bSandboxAdapter = class extends SandboxAdapter {
397
517
  const existing = await this.findMatchingSandbox();
398
518
  if (existing) {
399
519
  this.sandbox = existing;
520
+ this.wasFoundFlag = true;
400
521
  return;
401
522
  }
402
523
  const template = resolveSandboxImage(this.options.image);
@@ -422,7 +543,7 @@ var E2bSandboxAdapter = class extends SandboxAdapter {
422
543
  });
423
544
  }
424
545
  async run(command, options) {
425
- await this.ensureProvisioned();
546
+ this.requireProvisioned();
426
547
  const sandbox = this.requireSandbox();
427
548
  const { CommandExitError } = await loadE2bModule();
428
549
  if (options?.pty) {
@@ -450,7 +571,7 @@ var E2bSandboxAdapter = class extends SandboxAdapter {
450
571
  }
451
572
  }
452
573
  async runAsync(command, options) {
453
- await this.ensureProvisioned();
574
+ this.requireProvisioned();
454
575
  const sandbox = this.requireSandbox();
455
576
  const { CommandExitError } = await loadE2bModule();
456
577
  const queue = new AsyncQueue();
@@ -520,6 +641,7 @@ var E2bSandboxAdapter = class extends SandboxAdapter {
520
641
  queue.fail(error);
521
642
  throw error;
522
643
  });
644
+ suppressUnhandledRejection(completion);
523
645
  return {
524
646
  id: String(handle.pid),
525
647
  raw: handle,
@@ -585,6 +707,7 @@ var E2bSandboxAdapter = class extends SandboxAdapter {
585
707
  queue.fail(error);
586
708
  throw error;
587
709
  });
710
+ suppressUnhandledRejection(completion);
588
711
  return {
589
712
  id: String(handle.pid),
590
713
  raw: handle,
@@ -624,7 +747,7 @@ var E2bSandboxAdapter = class extends SandboxAdapter {
624
747
  return sandboxes;
625
748
  }
626
749
  async snapshot() {
627
- await this.ensureProvisioned();
750
+ this.requireProvisioned();
628
751
  const sandbox = this.requireSandbox();
629
752
  const snapshot = await sandbox.createSnapshot();
630
753
  return snapshot.snapshotId;
@@ -644,11 +767,27 @@ var E2bSandboxAdapter = class extends SandboxAdapter {
644
767
  void _port;
645
768
  }
646
769
  async getPreviewLink(port) {
647
- await this.ensureProvisioned();
770
+ this.requireProvisioned();
648
771
  const sandbox = this.requireSandbox();
649
772
  const host = sandbox.getHost(port);
650
773
  return host.startsWith("localhost:") ? `http://${host}` : `https://${host}`;
651
774
  }
775
+ async uploadFile(content, targetPath) {
776
+ this.requireProvisioned();
777
+ const sandbox = this.requireSandbox();
778
+ if (typeof content === "string") {
779
+ await sandbox.files.write(targetPath, content);
780
+ return;
781
+ }
782
+ const exactBytes = Uint8Array.from(content);
783
+ await sandbox.files.write(targetPath, new Blob([exactBytes]));
784
+ }
785
+ async downloadFile(sourcePath) {
786
+ this.requireProvisioned();
787
+ const sandbox = this.requireSandbox();
788
+ const bytes = await sandbox.files.read(sourcePath, { format: "bytes" });
789
+ return Buffer.from(bytes);
790
+ }
652
791
  async findMatchingSandbox() {
653
792
  const { Sandbox: E2bSandbox } = await loadE2bModule();
654
793
  const matches = await this.list();
@@ -684,7 +823,14 @@ var E2bSandboxAdapter = class extends SandboxAdapter {
684
823
  return {
685
824
  cwd: options?.cwd ?? this.workingDir,
686
825
  envs: this.getMergedEnv(options?.env),
687
- timeoutMs: options?.timeoutMs
826
+ // E2B is the only provider whose underlying SDK applies its own
827
+ // per-command timeout (60_000 ms) when nothing is specified.
828
+ // local-docker / modal / daytona pass through `undefined` and
829
+ // let the caller decide, so we do the same here by disabling
830
+ // E2B's default with `0`. Callers that want a wall-clock cap
831
+ // pass `timeoutMs` explicitly; everyone else relies on the
832
+ // sandbox lifecycle / their own AbortController.
833
+ timeoutMs: options?.timeoutMs ?? 0
688
834
  };
689
835
  }
690
836
  resolveTimeoutConfig() {
@@ -762,7 +908,7 @@ var LocalDockerSandboxAdapter = class extends SandboxAdapter {
762
908
  client = new Docker();
763
909
  container;
764
910
  get provider() {
765
- return "local-docker";
911
+ return SandboxProvider.LocalDocker;
766
912
  }
767
913
  get raw() {
768
914
  return {
@@ -789,11 +935,11 @@ var LocalDockerSandboxAdapter = class extends SandboxAdapter {
789
935
  const env = Object.entries(this.getMergedEnv()).map(
790
936
  ([key, value]) => `${key}=${value}`
791
937
  );
792
- const publishedPorts = this.options.provider?.publishedPorts ?? [];
938
+ const publishedPorts = this.resolveDefaultPublishedPorts();
793
939
  const portBindings = publishedPorts.length > 0 ? Object.fromEntries(
794
940
  publishedPorts.map((port) => [
795
941
  `${port}/tcp`,
796
- [{ HostIp: "127.0.0.1", HostPort: String(port) }]
942
+ [{ HostIp: "127.0.0.1", HostPort: "" }]
797
943
  ])
798
944
  ) : void 0;
799
945
  const exposedPorts = publishedPorts.length > 0 ? Object.fromEntries(publishedPorts.map((port) => [`${port}/tcp`, {}])) : void 0;
@@ -821,7 +967,7 @@ var LocalDockerSandboxAdapter = class extends SandboxAdapter {
821
967
  await container.start();
822
968
  }
823
969
  async run(command, options) {
824
- await this.ensureProvisioned();
970
+ this.requireProvisioned();
825
971
  const container = this.requireContainer();
826
972
  const exec = await container.exec({
827
973
  AttachStdout: true,
@@ -864,7 +1010,7 @@ var LocalDockerSandboxAdapter = class extends SandboxAdapter {
864
1010
  };
865
1011
  }
866
1012
  async runAsync(command, options) {
867
- await this.ensureProvisioned();
1013
+ this.requireProvisioned();
868
1014
  const container = this.requireContainer();
869
1015
  const exec = await container.exec({
870
1016
  AttachStdin: true,
@@ -921,6 +1067,7 @@ var LocalDockerSandboxAdapter = class extends SandboxAdapter {
921
1067
  queue.fail(error);
922
1068
  throw error;
923
1069
  });
1070
+ suppressUnhandledRejection(completion);
924
1071
  return {
925
1072
  id: exec.id,
926
1073
  raw: { exec, stream },
@@ -1017,15 +1164,26 @@ var LocalDockerSandboxAdapter = class extends SandboxAdapter {
1017
1164
  if (networkMode === "host") {
1018
1165
  return `http://127.0.0.1:${port}`;
1019
1166
  }
1020
- if (this.options.provider?.publishedPorts?.includes(port)) {
1021
- return `http://127.0.0.1:${port}`;
1167
+ const declared = this.resolveDefaultPublishedPorts();
1168
+ if (!declared.includes(port)) {
1169
+ throw new Error(
1170
+ `Port ${port} is not reachable from the host. Use local-docker provider.networkMode="host" or provider.publishedPorts to expose it.`
1171
+ );
1022
1172
  }
1023
- throw new Error(
1024
- `Port ${port} is not reachable from the host. Use local-docker provider.networkMode="host" or provider.publishedPorts to expose it.`
1025
- );
1173
+ const container = this.requireContainer();
1174
+ const inspect = await container.inspect();
1175
+ const portsMap = inspect.NetworkSettings?.Ports ?? {};
1176
+ const bindings = portsMap[`${port}/tcp`];
1177
+ const hostPort = Array.isArray(bindings) ? bindings.find((binding) => binding?.HostPort)?.HostPort : void 0;
1178
+ if (!hostPort) {
1179
+ throw new Error(
1180
+ `Port ${port} is not bound on the local-docker container. Make sure it is listed in provider.publishedPorts (or covered by AGENT_RESERVED_PORTS) before findOrProvision().`
1181
+ );
1182
+ }
1183
+ return `http://127.0.0.1:${hostPort}`;
1026
1184
  }
1027
1185
  async uploadFile(content, targetPath) {
1028
- await this.ensureProvisioned();
1186
+ this.requireProvisioned();
1029
1187
  const container = this.requireContainer();
1030
1188
  const pack = tar.pack();
1031
1189
  const body = Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8");
@@ -1034,7 +1192,7 @@ var LocalDockerSandboxAdapter = class extends SandboxAdapter {
1034
1192
  await container.putArchive(pack, { path: "/" });
1035
1193
  }
1036
1194
  async downloadFile(sourcePath) {
1037
- await this.ensureProvisioned();
1195
+ this.requireProvisioned();
1038
1196
  const container = this.requireContainer();
1039
1197
  const archive = await container.getArchive({ path: sourcePath });
1040
1198
  const chunks = [];
@@ -1049,6 +1207,32 @@ var LocalDockerSandboxAdapter = class extends SandboxAdapter {
1049
1207
  }
1050
1208
  return this.container;
1051
1209
  }
1210
+ /**
1211
+ * Local-docker requires ports to be declared at container creation
1212
+ * time (the `PortBindings` host config can't be amended on a running
1213
+ * container). To make `openPort` work predictably across providers,
1214
+ * we pre-publish all well-known agent-harness ports on every
1215
+ * local-docker sandbox we create — mirroring the Modal adapter's
1216
+ * `resolveDefaultUnencryptedPorts` behavior. Any explicit
1217
+ * `provider.publishedPorts` is honored AND merged with the reserved
1218
+ * set, so callers don't have to remember to list the agent's
1219
+ * default port for things like the claude-code SDK relay.
1220
+ *
1221
+ * Network-mode "host" containers don't use port bindings at all, so
1222
+ * we skip the merge in that case.
1223
+ */
1224
+ resolveDefaultPublishedPorts() {
1225
+ if (this.options.provider?.networkMode === "host") {
1226
+ return [];
1227
+ }
1228
+ const declared = this.options.provider?.publishedPorts;
1229
+ const reserved = collectAllAgentReservedPorts();
1230
+ const merged = new Set(declared ?? []);
1231
+ for (const port of reserved) {
1232
+ merged.add(port);
1233
+ }
1234
+ return Array.from(merged);
1235
+ }
1052
1236
  getLabels() {
1053
1237
  return {
1054
1238
  "agentbox.provider": this.provider,
@@ -1101,10 +1285,46 @@ var LocalDockerSandboxAdapter = class extends SandboxAdapter {
1101
1285
 
1102
1286
  // src/sandboxes/providers/modal.ts
1103
1287
  import { ModalClient } from "modal";
1288
+
1289
+ // src/sandboxes/tarball.ts
1290
+ import tar2 from "tar-stream";
1291
+ async function buildTarball(entries) {
1292
+ const pack = tar2.pack();
1293
+ const chunks = [];
1294
+ pack.on("data", (chunk) => {
1295
+ chunks.push(chunk);
1296
+ });
1297
+ const finished2 = new Promise((resolve, reject) => {
1298
+ pack.on("end", () => resolve());
1299
+ pack.on("error", (error) => reject(error));
1300
+ });
1301
+ for (const entry of entries) {
1302
+ const content = typeof entry.content === "string" ? Buffer.from(entry.content, "utf8") : entry.content;
1303
+ pack.entry(
1304
+ {
1305
+ name: entry.path.replace(/^\/+/, ""),
1306
+ mode: entry.mode ?? 420,
1307
+ size: content.length,
1308
+ mtime: /* @__PURE__ */ new Date(0),
1309
+ type: "file"
1310
+ },
1311
+ content
1312
+ );
1313
+ }
1314
+ pack.finalize();
1315
+ await finished2;
1316
+ return Buffer.concat(chunks);
1317
+ }
1318
+
1319
+ // src/sandboxes/providers/modal.ts
1104
1320
  var ModalSandboxAdapter = class extends SandboxAdapter {
1105
1321
  client;
1106
1322
  sandbox;
1107
1323
  clientClosed = false;
1324
+ // Cached tunnel map. Populated on the first `getPreviewLink` call after
1325
+ // provision; reused on every subsequent call so the agent runtime path
1326
+ // doesn't re-issue the Modal RPC for each per-run tunnel lookup.
1327
+ tunnelsPromise;
1108
1328
  constructor(options) {
1109
1329
  super(options);
1110
1330
  this.client = new ModalClient({
@@ -1115,7 +1335,7 @@ var ModalSandboxAdapter = class extends SandboxAdapter {
1115
1335
  });
1116
1336
  }
1117
1337
  get provider() {
1118
- return "modal";
1338
+ return SandboxProvider.Modal;
1119
1339
  }
1120
1340
  get raw() {
1121
1341
  return {
@@ -1130,6 +1350,7 @@ var ModalSandboxAdapter = class extends SandboxAdapter {
1130
1350
  const existing = await this.findMatchingSandbox();
1131
1351
  if (existing) {
1132
1352
  this.sandbox = existing;
1353
+ this.wasFoundFlag = true;
1133
1354
  return;
1134
1355
  }
1135
1356
  const appName = this.options.provider?.appName ?? "agentbox";
@@ -1139,7 +1360,9 @@ var ModalSandboxAdapter = class extends SandboxAdapter {
1139
1360
  });
1140
1361
  const image = await this.resolveModalImage();
1141
1362
  const resources = resolveSandboxResources(this.options.resources);
1363
+ const unencryptedPorts = this.resolveDefaultUnencryptedPorts();
1142
1364
  const sandbox = await this.client.sandboxes.create(app, image, {
1365
+ ...this.options.provider?.createParams,
1143
1366
  cpu: resources?.cpu,
1144
1367
  memoryMiB: resources?.memoryMiB,
1145
1368
  timeoutMs: this.options.autoStopMs,
@@ -1148,17 +1371,39 @@ var ModalSandboxAdapter = class extends SandboxAdapter {
1148
1371
  command: this.options.provider?.command ?? ["sleep", "infinity"],
1149
1372
  env: this.getMergedEnv(),
1150
1373
  encryptedPorts: this.options.provider?.encryptedPorts,
1151
- unencryptedPorts: this.options.provider?.unencryptedPorts,
1374
+ unencryptedPorts,
1152
1375
  verbose: this.options.provider?.verbose
1153
1376
  });
1154
1377
  await sandbox.setTags(this.getTags());
1155
1378
  this.sandbox = sandbox;
1156
1379
  }
1380
+ /**
1381
+ * Modal requires ports to be declared at sandbox creation time — a running
1382
+ * sandbox cannot gain new tunnels. To make `openPort` work predictably
1383
+ * across providers, we pre-declare all well-known agent-harness ports on
1384
+ * every Modal sandbox we create, unless the caller has explicitly pinned
1385
+ * them to a specific (possibly empty) list.
1386
+ */
1387
+ resolveDefaultUnencryptedPorts() {
1388
+ const declared = this.options.provider?.unencryptedPorts;
1389
+ const encrypted = new Set(this.options.provider?.encryptedPorts ?? []);
1390
+ const reserved = collectAllAgentReservedPorts().filter(
1391
+ (port) => !encrypted.has(port)
1392
+ );
1393
+ if (declared === void 0) {
1394
+ return reserved.length > 0 ? reserved : void 0;
1395
+ }
1396
+ const merged = new Set(declared);
1397
+ for (const port of reserved) {
1398
+ merged.add(port);
1399
+ }
1400
+ return Array.from(merged);
1401
+ }
1157
1402
  async run(command, options) {
1158
- await this.ensureProvisioned();
1403
+ this.requireProvisioned();
1159
1404
  const sandbox = this.requireSandbox();
1160
1405
  const process2 = await sandbox.exec(
1161
- ["/bin/sh", "-lc", toShellCommand(command)],
1406
+ ["/bin/sh", "-c", toShellCommand(command)],
1162
1407
  {
1163
1408
  workdir: options?.cwd ?? this.workingDir,
1164
1409
  timeoutMs: options?.timeoutMs,
@@ -1181,10 +1426,10 @@ var ModalSandboxAdapter = class extends SandboxAdapter {
1181
1426
  };
1182
1427
  }
1183
1428
  async runAsync(command, options) {
1184
- await this.ensureProvisioned();
1429
+ this.requireProvisioned();
1185
1430
  const sandbox = this.requireSandbox();
1186
1431
  const process2 = await sandbox.exec(
1187
- ["/bin/sh", "-lc", toShellCommand(command)],
1432
+ ["/bin/sh", "-c", toShellCommand(command)],
1188
1433
  {
1189
1434
  workdir: options?.cwd ?? this.workingDir,
1190
1435
  timeoutMs: options?.timeoutMs,
@@ -1230,6 +1475,7 @@ var ModalSandboxAdapter = class extends SandboxAdapter {
1230
1475
  queue.fail(error);
1231
1476
  throw error;
1232
1477
  });
1478
+ suppressUnhandledRejection(completion);
1233
1479
  return {
1234
1480
  id: `${sandbox.sandboxId}:${Date.now()}`,
1235
1481
  raw: process2,
@@ -1246,6 +1492,49 @@ var ModalSandboxAdapter = class extends SandboxAdapter {
1246
1492
  [Symbol.asyncIterator]: () => queue[Symbol.asyncIterator]()
1247
1493
  };
1248
1494
  }
1495
+ /**
1496
+ * Upload `files` as a tarball piped through stdin to a single in-sandbox
1497
+ * `tar -x` invocation, then exec `command` — all in one Modal `exec`
1498
+ * round-trip. This collapses the typical "N writeArtifact RPCs +
1499
+ * runCommand" setup pattern (~25 RPCs on cold paths, ~6s wall) into a
1500
+ * single ~1s call dominated by the actual install work.
1501
+ */
1502
+ async uploadAndRun(files, command, options) {
1503
+ this.requireProvisioned();
1504
+ const sandbox = this.requireSandbox();
1505
+ const tar3 = await buildTarball(files);
1506
+ const wrapped = `set -e
1507
+ tar -xf - -C /
1508
+ ${command}`;
1509
+ const process2 = await sandbox.exec(["/bin/sh", "-c", wrapped], {
1510
+ workdir: options?.cwd ?? this.workingDir,
1511
+ timeoutMs: options?.timeoutMs,
1512
+ env: this.getMergedEnv(options?.env),
1513
+ mode: "binary"
1514
+ });
1515
+ const writer = process2.stdin.getWriter();
1516
+ try {
1517
+ await writer.write(tar3);
1518
+ await writer.close();
1519
+ } finally {
1520
+ try {
1521
+ writer.releaseLock();
1522
+ } catch {
1523
+ }
1524
+ }
1525
+ const [stdout, stderr, exitCode] = await Promise.all([
1526
+ readStreamAsText(process2.stdout),
1527
+ readStreamAsText(process2.stderr),
1528
+ process2.wait()
1529
+ ]);
1530
+ return {
1531
+ exitCode,
1532
+ stdout,
1533
+ stderr,
1534
+ combinedOutput: `${stdout}${stderr}`,
1535
+ raw: process2
1536
+ };
1537
+ }
1249
1538
  async list(options) {
1250
1539
  const sandboxes = [];
1251
1540
  for await (const sandbox of this.client.sandboxes.list({
@@ -1262,7 +1551,7 @@ var ModalSandboxAdapter = class extends SandboxAdapter {
1262
1551
  return sandboxes;
1263
1552
  }
1264
1553
  async snapshot() {
1265
- await this.ensureProvisioned();
1554
+ this.requireProvisioned();
1266
1555
  const sandbox = this.requireSandbox();
1267
1556
  const image = await sandbox.snapshotFilesystem();
1268
1557
  return image.imageId;
@@ -1274,6 +1563,7 @@ var ModalSandboxAdapter = class extends SandboxAdapter {
1274
1563
  }
1275
1564
  await sandbox.terminate();
1276
1565
  this.sandbox = void 0;
1566
+ this.tunnelsPromise = void 0;
1277
1567
  }
1278
1568
  async delete() {
1279
1569
  await this.stop();
@@ -1284,12 +1574,44 @@ var ModalSandboxAdapter = class extends SandboxAdapter {
1284
1574
  if (provider.encryptedPorts?.includes(port)) {
1285
1575
  return;
1286
1576
  }
1287
- provider.unencryptedPorts = provider.unencryptedPorts?.includes(port) ? provider.unencryptedPorts : [...provider.unencryptedPorts ?? [], port];
1577
+ const alreadyDeclared = provider.unencryptedPorts?.includes(port) ?? false;
1578
+ if (!alreadyDeclared) {
1579
+ provider.unencryptedPorts = [...provider.unencryptedPorts ?? [], port];
1580
+ }
1581
+ if (!this.sandbox) {
1582
+ return;
1583
+ }
1584
+ if (alreadyDeclared) {
1585
+ return;
1586
+ }
1587
+ try {
1588
+ if (!this.tunnelsPromise) {
1589
+ this.tunnelsPromise = this.sandbox.tunnels();
1590
+ }
1591
+ const tunnels = await this.tunnelsPromise;
1592
+ if (tunnels[port]) {
1593
+ return;
1594
+ }
1595
+ } catch {
1596
+ return;
1597
+ }
1598
+ throw new Error(
1599
+ `Modal sandbox is already running and cannot expose port ${port} dynamically. Declare it at creation time via \`provider.unencryptedPorts\` (e.g. \`provider: { unencryptedPorts: [${port}] }\`) or use \`AGENT_RESERVED_PORTS\` / \`collectAllAgentReservedPorts()\` from agentbox-sdk to pre-declare the agent harness ports.`
1600
+ );
1288
1601
  }
1289
1602
  async getPreviewLink(port) {
1290
- await this.ensureProvisioned();
1603
+ this.requireProvisioned();
1291
1604
  const sandbox = this.requireSandbox();
1292
- const tunnels = await sandbox.tunnels();
1605
+ if (!this.tunnelsPromise) {
1606
+ this.tunnelsPromise = sandbox.tunnels();
1607
+ }
1608
+ let tunnels;
1609
+ try {
1610
+ tunnels = await this.tunnelsPromise;
1611
+ } catch (error) {
1612
+ this.tunnelsPromise = void 0;
1613
+ throw error;
1614
+ }
1293
1615
  const tunnel = tunnels[port];
1294
1616
  if (!tunnel) {
1295
1617
  throw new Error(`Modal sandbox does not expose port ${port}.`);
@@ -1347,22 +1669,335 @@ var ModalSandboxAdapter = class extends SandboxAdapter {
1347
1669
  }
1348
1670
  };
1349
1671
 
1672
+ // src/sandboxes/providers/vercel.ts
1673
+ import { Sandbox as VercelSandbox } from "@vercel/sandbox";
1674
+ function pickFirstTag(tags) {
1675
+ if (!tags) return void 0;
1676
+ const entries = Object.entries(tags);
1677
+ if (entries.length === 0) return void 0;
1678
+ const [key, value] = entries[0];
1679
+ return { [key]: value };
1680
+ }
1681
+ function matchesAllTags(candidateTags, required) {
1682
+ return Object.entries(required).every(
1683
+ ([key, value]) => candidateTags?.[key] === value
1684
+ );
1685
+ }
1686
+ function describeVercelApiError(error, action) {
1687
+ if (error && typeof error === "object" && "response" in error && error.response instanceof Response) {
1688
+ const apiError = error;
1689
+ const status = apiError.response.status;
1690
+ const body = apiError.json !== void 0 ? JSON.stringify(apiError.json) : apiError.text ?? "";
1691
+ return new Error(
1692
+ `Vercel ${action} failed with HTTP ${status}: ${body || apiError.message}`
1693
+ );
1694
+ }
1695
+ return error instanceof Error ? error : new Error(String(error));
1696
+ }
1697
+ async function wrapVercelApiError(action, fn) {
1698
+ try {
1699
+ return await fn();
1700
+ } catch (error) {
1701
+ throw describeVercelApiError(error, action);
1702
+ }
1703
+ }
1704
+ function buildTimeoutSignal(timeoutMs) {
1705
+ if (!timeoutMs || timeoutMs <= 0) return void 0;
1706
+ return AbortSignal.timeout(timeoutMs);
1707
+ }
1708
+ function getCredentials(options) {
1709
+ const token = options.provider?.token ?? process.env.VERCEL_TOKEN;
1710
+ const teamId = options.provider?.teamId ?? process.env.VERCEL_TEAM_ID;
1711
+ const projectId = options.provider?.projectId ?? process.env.VERCEL_PROJECT_ID;
1712
+ if (token && teamId && projectId) {
1713
+ return { token, teamId, projectId };
1714
+ }
1715
+ return {};
1716
+ }
1717
+ var VercelSandboxAdapter = class extends SandboxAdapter {
1718
+ sandbox;
1719
+ get provider() {
1720
+ return SandboxProvider.Vercel;
1721
+ }
1722
+ get raw() {
1723
+ return { sandbox: this.sandbox };
1724
+ }
1725
+ get id() {
1726
+ return this.sandbox?.name;
1727
+ }
1728
+ get workingDir() {
1729
+ return this.options.workingDir ?? "/vercel/sandbox";
1730
+ }
1731
+ get previewHeaders() {
1732
+ const token = this.options.provider?.protectionBypass;
1733
+ return token ? { "x-vercel-protection-bypass": token } : {};
1734
+ }
1735
+ async provision() {
1736
+ const existing = await this.findExistingSandbox();
1737
+ if (existing) {
1738
+ this.sandbox = existing;
1739
+ return;
1740
+ }
1741
+ const credentials = getCredentials(this.options);
1742
+ const provider = this.options.provider;
1743
+ const snapshotId = provider?.snapshotId;
1744
+ const timeout = provider?.timeoutMs ?? 12e4;
1745
+ const runtime = provider?.runtime ?? "node24";
1746
+ const resources = resolveSandboxResources(this.options.resources);
1747
+ const vcpus = resources?.cpu ? { resources: { vcpus: resources.cpu } } : {};
1748
+ const base = {
1749
+ ...credentials,
1750
+ timeout,
1751
+ env: this.getMergedEnv(),
1752
+ ...vcpus,
1753
+ tags: this.getTags(),
1754
+ ...provider?.ports?.length ? { ports: provider.ports } : {}
1755
+ };
1756
+ const sandbox = await wrapVercelApiError("create sandbox", () => {
1757
+ if (snapshotId) {
1758
+ return VercelSandbox.create({
1759
+ ...base,
1760
+ source: { type: "snapshot", snapshotId }
1761
+ });
1762
+ }
1763
+ if (provider?.gitSource) {
1764
+ const git = provider.gitSource;
1765
+ const source = {
1766
+ type: "git",
1767
+ url: git.url,
1768
+ depth: git.depth,
1769
+ revision: git.revision,
1770
+ ...git.username && git.password ? { username: git.username, password: git.password } : {}
1771
+ };
1772
+ return VercelSandbox.create({ ...base, runtime, source });
1773
+ }
1774
+ return VercelSandbox.create({ ...base, runtime });
1775
+ });
1776
+ this.sandbox = sandbox;
1777
+ if (this.workingDir !== "/vercel/sandbox") {
1778
+ await wrapVercelApiError(
1779
+ "create working directory",
1780
+ () => sandbox.runCommand({
1781
+ cmd: "mkdir",
1782
+ args: ["-p", this.workingDir],
1783
+ sudo: true
1784
+ })
1785
+ );
1786
+ }
1787
+ }
1788
+ async run(command, options) {
1789
+ this.requireProvisioned();
1790
+ const sandbox = this.requireSandbox();
1791
+ const signal = buildTimeoutSignal(options?.timeoutMs);
1792
+ const result = await wrapVercelApiError(
1793
+ "run command",
1794
+ () => sandbox.runCommand({
1795
+ cmd: "sh",
1796
+ args: ["-lc", toShellCommand(command)],
1797
+ cwd: options?.cwd ?? this.workingDir,
1798
+ env: this.getMergedEnv(options?.env),
1799
+ ...signal ? { signal } : {}
1800
+ })
1801
+ );
1802
+ const [stdout, stderr] = await Promise.all([
1803
+ result.stdout(),
1804
+ result.stderr()
1805
+ ]);
1806
+ return {
1807
+ exitCode: result.exitCode,
1808
+ stdout,
1809
+ stderr,
1810
+ combinedOutput: `${stdout}${stderr}`,
1811
+ raw: result
1812
+ };
1813
+ }
1814
+ async runAsync(command, options) {
1815
+ this.requireProvisioned();
1816
+ const sandbox = this.requireSandbox();
1817
+ const signal = buildTimeoutSignal(options?.timeoutMs);
1818
+ const cmd = await wrapVercelApiError(
1819
+ "start async command",
1820
+ () => sandbox.runCommand({
1821
+ cmd: "sh",
1822
+ args: ["-lc", toShellCommand(command)],
1823
+ cwd: options?.cwd ?? this.workingDir,
1824
+ env: this.getMergedEnv(options?.env),
1825
+ detached: true,
1826
+ ...signal ? { signal } : {}
1827
+ })
1828
+ );
1829
+ const queue = new AsyncQueue();
1830
+ let stdout = "";
1831
+ let stderr = "";
1832
+ const completion = (async () => {
1833
+ for await (const log of cmd.logs()) {
1834
+ if (log.stream === "stdout") {
1835
+ stdout += log.data;
1836
+ queue.push({
1837
+ type: "stdout",
1838
+ chunk: log.data,
1839
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1840
+ });
1841
+ } else {
1842
+ stderr += log.data;
1843
+ queue.push({
1844
+ type: "stderr",
1845
+ chunk: log.data,
1846
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1847
+ });
1848
+ }
1849
+ }
1850
+ const finished2 = await cmd.wait();
1851
+ const exitCode = finished2.exitCode;
1852
+ queue.push({
1853
+ type: "exit",
1854
+ exitCode,
1855
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1856
+ });
1857
+ queue.finish();
1858
+ return {
1859
+ exitCode,
1860
+ stdout,
1861
+ stderr,
1862
+ combinedOutput: `${stdout}${stderr}`,
1863
+ raw: finished2
1864
+ };
1865
+ })().catch((error) => {
1866
+ queue.fail(error);
1867
+ throw error;
1868
+ });
1869
+ suppressUnhandledRejection(completion);
1870
+ return {
1871
+ id: cmd.cmdId,
1872
+ raw: cmd,
1873
+ wait: () => completion,
1874
+ kill: async () => {
1875
+ await cmd.kill();
1876
+ },
1877
+ [Symbol.asyncIterator]: () => queue[Symbol.asyncIterator]()
1878
+ };
1879
+ }
1880
+ async list(options) {
1881
+ const filterTags = options?.tags ?? this.getTags();
1882
+ const sandboxes = await this.listSandboxesByTags(filterTags);
1883
+ return sandboxes.map(
1884
+ (s) => ({
1885
+ provider: this.provider,
1886
+ id: s.name,
1887
+ state: s.status,
1888
+ tags: s.tags ?? {},
1889
+ createdAt: new Date(s.createdAt).toISOString(),
1890
+ raw: s
1891
+ })
1892
+ );
1893
+ }
1894
+ async snapshot() {
1895
+ this.requireProvisioned();
1896
+ const sandbox = this.requireSandbox();
1897
+ const snap = await wrapVercelApiError(
1898
+ "snapshot sandbox",
1899
+ () => sandbox.snapshot()
1900
+ );
1901
+ return snap.snapshotId;
1902
+ }
1903
+ async stop() {
1904
+ const sandbox = this.sandbox;
1905
+ if (!sandbox) {
1906
+ return;
1907
+ }
1908
+ await wrapVercelApiError("stop sandbox", () => sandbox.stop());
1909
+ this.sandbox = void 0;
1910
+ }
1911
+ async delete() {
1912
+ await this.stop();
1913
+ }
1914
+ async openPort(_port) {
1915
+ void _port;
1916
+ }
1917
+ async getPreviewLink(port) {
1918
+ this.requireProvisioned();
1919
+ const sandbox = this.requireSandbox();
1920
+ return sandbox.domain(port);
1921
+ }
1922
+ async uploadFile(content, targetPath) {
1923
+ this.requireProvisioned();
1924
+ const sandbox = this.requireSandbox();
1925
+ const data = typeof content === "string" ? content : new Uint8Array(content);
1926
+ await sandbox.writeFiles([{ path: targetPath, content: data }]);
1927
+ }
1928
+ async downloadFile(sourcePath) {
1929
+ this.requireProvisioned();
1930
+ const sandbox = this.requireSandbox();
1931
+ const result = await sandbox.readFileToBuffer({ path: sourcePath });
1932
+ if (!result) {
1933
+ throw new Error(`File not found in Vercel sandbox: ${sourcePath}`);
1934
+ }
1935
+ return result;
1936
+ }
1937
+ getTags() {
1938
+ return {
1939
+ "agentbox.provider": this.provider,
1940
+ ...this.options.tags ?? {}
1941
+ };
1942
+ }
1943
+ async listSandboxesByTags(tags) {
1944
+ const credentials = getCredentials(this.options);
1945
+ const result = await wrapVercelApiError(
1946
+ "list sandboxes",
1947
+ () => VercelSandbox.list({
1948
+ ...credentials,
1949
+ tags: pickFirstTag(tags)
1950
+ })
1951
+ );
1952
+ return result.sandboxes.filter(
1953
+ (s) => matchesAllTags(s.tags, tags)
1954
+ );
1955
+ }
1956
+ async findExistingSandbox() {
1957
+ const credentials = getCredentials(this.options);
1958
+ const sandboxes = await this.listSandboxesByTags(this.getTags());
1959
+ const match = sandboxes.find((s) => s.status === "running");
1960
+ if (!match) {
1961
+ return void 0;
1962
+ }
1963
+ return wrapVercelApiError(
1964
+ "get sandbox",
1965
+ () => VercelSandbox.get({ ...credentials, name: match.name })
1966
+ );
1967
+ }
1968
+ requireSandbox() {
1969
+ if (!this.sandbox) {
1970
+ throw new Error("Vercel sandbox has not been provisioned.");
1971
+ }
1972
+ return this.sandbox;
1973
+ }
1974
+ };
1975
+
1350
1976
  // src/sandboxes/Sandbox.ts
1977
+ function shortLabel(command) {
1978
+ const oneLine = Array.isArray(command) ? command.join(" ") : command;
1979
+ const cleaned = oneLine.replace(/\s+/g, " ").trim();
1980
+ return cleaned.length > 80 ? `${cleaned.slice(0, 80)}\u2026` : cleaned;
1981
+ }
1351
1982
  function createSandboxAdapter(provider, options) {
1352
1983
  switch (provider) {
1353
- case "local-docker":
1984
+ case SandboxProvider.LocalDocker:
1354
1985
  return new LocalDockerSandboxAdapter(
1355
1986
  options
1356
1987
  );
1357
- case "modal":
1988
+ case SandboxProvider.Modal:
1358
1989
  return new ModalSandboxAdapter(
1359
1990
  options
1360
1991
  );
1361
- case "daytona":
1992
+ case SandboxProvider.Daytona:
1362
1993
  return new DaytonaSandboxAdapter(
1363
1994
  options
1364
1995
  );
1365
- case "e2b":
1996
+ case SandboxProvider.Vercel:
1997
+ return new VercelSandboxAdapter(
1998
+ options
1999
+ );
2000
+ case SandboxProvider.E2B:
1366
2001
  return new E2bSandboxAdapter(
1367
2002
  options
1368
2003
  );
@@ -1391,8 +2026,35 @@ var Sandbox = class {
1391
2026
  get raw() {
1392
2027
  return this.adapter.raw;
1393
2028
  }
2029
+ /**
2030
+ * Whether `findOrProvision()` warm-attached to a pre-existing tagged
2031
+ * sandbox (`true`) or created a fresh one (`false`). Useful to skip
2032
+ * idempotent setup that the previous run already performed (e.g.
2033
+ * `agent.setup()`). Always `false` before `findOrProvision()` resolves.
2034
+ */
2035
+ get wasFound() {
2036
+ return this.adapter.wasFound;
2037
+ }
2038
+ /**
2039
+ * Attach to an existing tagged sandbox or create a new one. Must be
2040
+ * called before `run`, `runAsync`, `gitClone`, `uploadAndRun`,
2041
+ * `getPreviewLink`, etc. Repeated calls are cheap (the result is
2042
+ * cached internally).
2043
+ */
2044
+ async findOrProvision() {
2045
+ await time(
2046
+ debugSandbox,
2047
+ `findOrProvision [${this.provider}]`,
2048
+ () => this.adapter.findOrProvision()
2049
+ );
2050
+ return this;
2051
+ }
1394
2052
  async openPort(port) {
1395
- await this.adapter.openPort(port);
2053
+ await time(
2054
+ debugSandbox,
2055
+ `openPort [${this.provider}] :${port}`,
2056
+ () => this.adapter.openPort(port)
2057
+ );
1396
2058
  return this;
1397
2059
  }
1398
2060
  setSecret(name, value) {
@@ -1407,10 +2069,19 @@ var Sandbox = class {
1407
2069
  return this.adapter.gitClone(options);
1408
2070
  }
1409
2071
  async run(command, options) {
1410
- return this.adapter.run(command, options);
2072
+ return time(
2073
+ debugSandbox,
2074
+ `run [${this.provider}] ${shortLabel(command)}`,
2075
+ () => this.adapter.run(command, options),
2076
+ (result) => ({ exit: result.exitCode })
2077
+ );
1411
2078
  }
1412
2079
  async runAsync(command, options) {
1413
- return this.adapter.runAsync(command, options);
2080
+ return time(
2081
+ debugSandbox,
2082
+ `runAsync [${this.provider}] ${shortLabel(command)}`,
2083
+ () => this.adapter.runAsync(command, options)
2084
+ );
1414
2085
  }
1415
2086
  async list(options) {
1416
2087
  return this.adapter.list(options);
@@ -1425,7 +2096,28 @@ var Sandbox = class {
1425
2096
  return this.adapter.delete();
1426
2097
  }
1427
2098
  async getPreviewLink(port) {
1428
- return this.adapter.getPreviewLink(port);
2099
+ return time(
2100
+ debugSandbox,
2101
+ `getPreviewLink [${this.provider}] :${port}`,
2102
+ () => this.adapter.getPreviewLink(port)
2103
+ );
2104
+ }
2105
+ get previewHeaders() {
2106
+ return this.adapter.previewHeaders;
2107
+ }
2108
+ async uploadFile(content, targetPath) {
2109
+ return this.adapter.uploadFile(content, targetPath);
2110
+ }
2111
+ async downloadFile(sourcePath) {
2112
+ return this.adapter.downloadFile(sourcePath);
2113
+ }
2114
+ async uploadAndRun(files, command, options) {
2115
+ return time(
2116
+ debugSandbox,
2117
+ `uploadAndRun [${this.provider}] ${shortLabel(command)}`,
2118
+ () => this.adapter.uploadAndRun(files, command, options),
2119
+ (result) => ({ exit: result.exitCode, files: files.length })
2120
+ );
1429
2121
  }
1430
2122
  };
1431
2123