@watasu/sdk 0.1.30 → 0.1.40

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/dist/sandbox.d.ts CHANGED
@@ -6,6 +6,7 @@ import { Git } from './git.js';
6
6
  import { Pty } from './pty.js';
7
7
  import { ProcessManager } from './process.js';
8
8
  import { TerminalManager } from './terminal.js';
9
+ import type { Volume } from './volume.js';
9
10
  export interface SandboxCreateOpts extends ConnectionOpts {
10
11
  /** Template slug to create. Defaults to "base". */
11
12
  template?: string;
@@ -22,7 +23,7 @@ export interface SandboxCreateOpts extends ConnectionOpts {
22
23
  /** Timeout lifecycle policy. Defaults to killing the sandbox at timeout. */
23
24
  lifecycle?: SandboxLifecycle;
24
25
  /** Persistent volumes to mount, keyed by guest path. */
25
- volumeMounts?: Record<string, string | {
26
+ volumeMounts?: Record<string, string | Volume | {
26
27
  name: string;
27
28
  }>;
28
29
  }
@@ -62,6 +63,7 @@ export interface SandboxListOpts extends ConnectionOpts {
62
63
  /** Team slug to list within. */
63
64
  team?: string;
64
65
  }
66
+ type SandboxRequestOpts = Pick<ConnectionOpts, 'requestTimeoutMs' | 'signal'>;
65
67
  export interface SandboxInfo {
66
68
  sandboxId: string;
67
69
  templateId?: string;
@@ -138,7 +140,6 @@ export interface SnapshotListOpts extends ConnectionOpts {
138
140
  export interface RestoreSnapshotOpts extends ConnectionOpts {
139
141
  checkpointId?: string | number;
140
142
  snapshotId?: string | number;
141
- timeout?: number;
142
143
  timeoutMs?: number;
143
144
  }
144
145
  /** Paginator for listing sandbox snapshots. */
@@ -172,14 +173,12 @@ export declare class Sandbox {
172
173
  /** Default sandbox lifetime in milliseconds. */
173
174
  static readonly defaultSandboxTimeoutMs = 300000;
174
175
  files: Filesystem;
175
- filesystem: Filesystem;
176
176
  commands: Commands;
177
177
  process: ProcessManager;
178
178
  pty: Pty;
179
179
  terminal: TerminalManager;
180
180
  git: Git;
181
181
  cwd: string | undefined;
182
- envVars: Record<string, string>;
183
182
  readonly sandboxId: string;
184
183
  private readonly mcpPort;
185
184
  private mcpToken;
@@ -202,18 +201,13 @@ export declare class Sandbox {
202
201
  static create(template: string, opts?: SandboxCreateOpts): Promise<Sandbox>;
203
202
  /** Connect to an existing sandbox and return it with a fresh data-plane session. */
204
203
  static connect(sandboxId: string, opts?: SandboxConnectOpts): Promise<Sandbox>;
205
- /** Alias for `connect`. */
206
- static reconnect(sandboxId: string, opts?: SandboxConnectOpts): Promise<Sandbox>;
207
- static reconnect(opts: SandboxConnectOpts & {
208
- sandboxID: string;
209
- }): Promise<Sandbox>;
210
204
  /** Refresh this sandbox's data-plane session in place. */
211
205
  connect(opts?: SandboxConnectOpts): Promise<this>;
212
206
  /** Resume a paused sandbox by id. */
213
207
  static resume(sandboxId: string, opts?: SandboxConnectOpts): Promise<boolean>;
214
208
  /** Pause a sandbox by id. Returns false when it was already paused. */
215
209
  static betaPause(sandboxId: string, opts?: ConnectionOpts): Promise<boolean>;
216
- /** Alias for `betaPause`. */
210
+ /** Pause a sandbox by id. */
217
211
  static pause(sandboxId: string, opts?: ConnectionOpts): Promise<boolean>;
218
212
  /** Destroy a sandbox by id. */
219
213
  static kill(sandboxId: string, opts?: ConnectionOpts | string): Promise<boolean>;
@@ -222,8 +216,6 @@ export declare class Sandbox {
222
216
  /** Atomically replace a sandbox's network egress policy by id. */
223
217
  static updateNetwork(sandboxId: string, network: SandboxNetworkUpdate, opts?: SandboxNetworkUpdateOpts): Promise<void>;
224
218
  private static putNetwork;
225
- /** Deprecated alias for `getInfo`. */
226
- static getFullInfo(sandboxId: string, opts?: ConnectionOpts): Promise<SandboxInfo>;
227
219
  /** Create a Watasu checkpoint using snapshot naming. */
228
220
  static createSnapshot(sandboxId: string, opts?: CreateSnapshotOpts): Promise<SnapshotInfo>;
229
221
  /** List snapshots visible to the configured API key. */
@@ -231,19 +223,17 @@ export declare class Sandbox {
231
223
  /** Delete a snapshot by id. Returns `false` when the snapshot does not exist. */
232
224
  static deleteSnapshot(snapshotId: string, opts?: ConnectionOpts): Promise<boolean>;
233
225
  /** Destroy this sandbox. */
234
- kill(): Promise<boolean>;
226
+ kill(opts?: SandboxRequestOpts): Promise<boolean>;
235
227
  /** Check if this sandbox is in a runtime-active lifecycle state. */
236
- isRunning(opts?: Pick<ConnectionOpts, 'requestTimeoutMs'>): Promise<boolean>;
228
+ isRunning(opts?: SandboxRequestOpts): Promise<boolean>;
237
229
  /** Set a sandbox's lifetime by id. */
238
230
  static setTimeout(sandboxId: string, timeoutMs: number, opts?: ConnectionOpts): Promise<void>;
239
231
  /** Set this sandbox's lifetime. */
240
- setTimeout(timeoutMs: number): Promise<void>;
241
- /** Keep the sandbox alive for `duration` milliseconds. */
242
- keepAlive(duration: number): Promise<void>;
232
+ setTimeout(timeoutMs: number, opts?: SandboxRequestOpts): Promise<void>;
243
233
  /** Fetch control-plane metadata for a sandbox by id. */
244
234
  static getInfo(sandboxId: string, opts?: ConnectionOpts): Promise<SandboxInfo>;
245
235
  /** Fetch the latest control-plane metadata for this sandbox. */
246
- getInfo(): Promise<SandboxInfo>;
236
+ getInfo(opts?: SandboxRequestOpts): Promise<SandboxInfo>;
247
237
  /** Fetch latest sandbox metrics. */
248
238
  getMetrics(opts?: SandboxMetricsOpts): Promise<SandboxMetrics[]>;
249
239
  /** Create a Watasu checkpoint using snapshot naming. */
@@ -260,16 +250,10 @@ export declare class Sandbox {
260
250
  static list(opts?: SandboxListOpts | string): SandboxPaginator;
261
251
  /** Return the public hostname for an exposed sandbox port. */
262
252
  getHost(port: number): string;
263
- /** Return the public hostname for the sandbox or an exposed sandbox port. */
264
- getHostname(port?: number): string;
265
253
  /** Return the conventional MCP URL for this sandbox. */
266
254
  getMcpUrl(): string;
267
255
  /** Return the MCP gateway token when the sandbox contains one. */
268
256
  getMcpToken(): Promise<string | undefined>;
269
- /** Return a protocol string for a secure or insecure sandbox URL. */
270
- getProtocol(baseProtocol?: string, secure?: boolean): string;
271
- /** Close the local SDK attachment. This does not destroy the sandbox. */
272
- close(): Promise<void>;
273
257
  /** Get a signed URL that accepts a POST upload for a sandbox file path. */
274
258
  uploadUrl(path?: string, opts?: SandboxUrlOpts): Promise<string>;
275
259
  /** Get a signed URL that accepts a GET download for a sandbox file path. */
@@ -282,7 +266,7 @@ export declare class Sandbox {
282
266
  updateNetwork(network: SandboxNetworkUpdate, opts?: SandboxNetworkUpdateOpts): Promise<void>;
283
267
  /** Pause this sandbox. Returns false when it was already paused. */
284
268
  betaPause(opts?: ConnectionOpts): Promise<boolean>;
285
- /** Alias for `betaPause`. */
269
+ /** Pause this sandbox. Returns false when it was already paused. */
286
270
  pause(opts?: ConnectionOpts): Promise<boolean>;
287
271
  /** Resume this sandbox and refresh its data-plane session. */
288
272
  resume(opts?: SandboxConnectOpts): Promise<boolean>;
@@ -295,3 +279,4 @@ export declare class Sandbox {
295
279
  protected runtimeDeleteJson(path: string, opts?: ConnectionOpts): Promise<Record<string, unknown>>;
296
280
  private configOptions;
297
281
  }
282
+ export {};
package/dist/sandbox.js CHANGED
@@ -24,6 +24,7 @@ export class SnapshotPaginator {
24
24
  const control = new ControlClient(config);
25
25
  const payload = await control.get(snapshotListPath(this.opts, this.nextToken), {
26
26
  requestTimeoutMs: opts.requestTimeoutMs,
27
+ signal: opts.signal,
27
28
  });
28
29
  this.nextToken = stringValue(payload.next_token ?? payload.nextToken);
29
30
  this.hasNext = this.nextToken !== undefined;
@@ -57,6 +58,7 @@ export class SandboxPaginator {
57
58
  const control = new ControlClient(config);
58
59
  const payload = await control.get(sandboxListPath(this.opts, this.nextToken), {
59
60
  requestTimeoutMs: opts.requestTimeoutMs,
61
+ signal: opts.signal,
60
62
  });
61
63
  this.nextToken = stringValue(payload.next_token ?? payload.nextToken);
62
64
  this.hasNext = this.nextToken !== undefined;
@@ -80,14 +82,12 @@ export class Sandbox {
80
82
  /** Default sandbox lifetime in milliseconds. */
81
83
  static defaultSandboxTimeoutMs = 300_000;
82
84
  files;
83
- filesystem;
84
85
  commands;
85
86
  process;
86
87
  pty;
87
88
  terminal;
88
89
  git;
89
90
  cwd;
90
- envVars;
91
91
  sandboxId;
92
92
  mcpPort = 50005;
93
93
  mcpToken;
@@ -101,12 +101,10 @@ export class Sandbox {
101
101
  this.config = opts.connectionConfig;
102
102
  this.control = opts.control ?? new ControlClient(this.config);
103
103
  this.envs = opts.envs ?? {};
104
- this.envVars = this.envs;
105
104
  this.sandbox = opts.sandbox ?? {};
106
105
  const dataPlane = dataPlaneFromSession(opts.session, this.config);
107
106
  this.dataPlane = dataPlane;
108
107
  this.files = new Filesystem(dataPlane);
109
- this.filesystem = this.files;
110
108
  this.commands = new Commands(dataPlane, this.config, this.envs);
111
109
  this.process = new ProcessManager(this.commands);
112
110
  this.pty = new Pty(dataPlane, this.config);
@@ -141,6 +139,7 @@ export class Sandbox {
141
139
  const response = await control.post('/sandboxes', {
142
140
  json: sandboxPayload,
143
141
  requestTimeoutMs: sessionOperationRequestTimeout(config, sandboxOpts),
142
+ signal: sandboxOpts.signal,
144
143
  });
145
144
  const sandbox = record(response.sandbox ?? response);
146
145
  const sandboxId = sandbox.id ?? sandbox.sandbox_id;
@@ -160,10 +159,14 @@ export class Sandbox {
160
159
  static async connect(sandboxId, opts = {}) {
161
160
  const config = new ConnectionConfig(opts);
162
161
  const control = new ControlClient(config);
163
- const info = await control.get(`/sandboxes/${sandboxId}`);
162
+ const info = await control.get(`/sandboxes/${sandboxId}`, {
163
+ requestTimeoutMs: opts.requestTimeoutMs,
164
+ signal: opts.signal,
165
+ });
164
166
  const response = await control.post(`/sandboxes/${sandboxId}/resume`, {
165
167
  json: opts.timeoutMs ? { timeout: Math.ceil(opts.timeoutMs / 1000) } : {},
166
168
  requestTimeoutMs: sessionOperationRequestTimeout(config, opts),
169
+ signal: opts.signal,
167
170
  });
168
171
  return new this({
169
172
  sandboxId,
@@ -173,22 +176,17 @@ export class Sandbox {
173
176
  sandbox: record(response.sandbox ?? info.sandbox ?? {}),
174
177
  });
175
178
  }
176
- static async reconnect(sandboxOrOpts, opts = {}) {
177
- if (typeof sandboxOrOpts === 'string')
178
- return this.connect(sandboxOrOpts, opts);
179
- return this.connect(sandboxOrOpts.sandboxID, sandboxOrOpts);
180
- }
181
179
  /** Refresh this sandbox's data-plane session in place. */
182
180
  async connect(opts = {}) {
183
181
  const response = await this.control.post(`/sandboxes/${this.sandboxId}/resume`, {
184
182
  json: opts.timeoutMs ? { timeout: Math.ceil(opts.timeoutMs / 1000) } : {},
185
183
  requestTimeoutMs: sessionOperationRequestTimeout(this.config, opts),
184
+ signal: opts.signal,
186
185
  });
187
186
  this.sandbox = record(response.sandbox ?? this.sandbox);
188
187
  const dataPlane = dataPlaneFromSession(response.session, this.config);
189
188
  this.dataPlane = dataPlane;
190
189
  this.files = new Filesystem(dataPlane);
191
- this.filesystem = this.files;
192
190
  this.commands = new Commands(dataPlane, this.config, this.envs);
193
191
  this.process = new ProcessManager(this.commands);
194
192
  this.pty = new Pty(dataPlane, this.config);
@@ -207,6 +205,7 @@ export class Sandbox {
207
205
  try {
208
206
  await control.post(`/sandboxes/${sandboxId}/pause`, {
209
207
  requestTimeoutMs: opts.requestTimeoutMs,
208
+ signal: opts.signal,
210
209
  });
211
210
  return true;
212
211
  }
@@ -216,14 +215,18 @@ export class Sandbox {
216
215
  throw error;
217
216
  }
218
217
  }
219
- /** Alias for `betaPause`. */
218
+ /** Pause a sandbox by id. */
220
219
  static async pause(sandboxId, opts = {}) {
221
220
  return this.betaPause(sandboxId, opts);
222
221
  }
223
222
  /** Destroy a sandbox by id. */
224
223
  static async kill(sandboxId, opts = {}) {
224
+ const requestOpts = typeof opts === 'string' ? {} : opts;
225
225
  const control = new ControlClient(new ConnectionConfig(typeof opts === 'string' ? { apiKey: opts } : opts));
226
- await control.delete(`/sandboxes/${sandboxId}`);
226
+ await control.delete(`/sandboxes/${sandboxId}`, {
227
+ requestTimeoutMs: requestOpts.requestTimeoutMs,
228
+ signal: requestOpts.signal,
229
+ });
227
230
  return true;
228
231
  }
229
232
  /** Fetch sandbox metrics by id. */
@@ -231,6 +234,7 @@ export class Sandbox {
231
234
  const control = new ControlClient(new ConnectionConfig(opts));
232
235
  const payload = await control.get(metricsPath(sandboxId, opts), {
233
236
  requestTimeoutMs: opts.requestTimeoutMs,
237
+ signal: opts.signal,
234
238
  });
235
239
  return metricsList(payload.metrics ?? payload);
236
240
  }
@@ -243,19 +247,17 @@ export class Sandbox {
243
247
  const response = await control.put(`/sandboxes/${sandboxId}/network`, {
244
248
  json: networkUpdatePayload(network),
245
249
  requestTimeoutMs: opts.requestTimeoutMs,
250
+ signal: opts.signal,
246
251
  });
247
252
  return response.sandbox === undefined ? undefined : record(response.sandbox);
248
253
  }
249
- /** Deprecated alias for `getInfo`. */
250
- static async getFullInfo(sandboxId, opts = {}) {
251
- return this.getInfo(sandboxId, opts);
252
- }
253
254
  /** Create a Watasu checkpoint using snapshot naming. */
254
255
  static async createSnapshot(sandboxId, opts = {}) {
255
256
  const control = new ControlClient(new ConnectionConfig(opts));
256
257
  const payload = await control.post(`/sandboxes/${sandboxId}/snapshots`, {
257
258
  json: snapshotPayload(opts),
258
259
  requestTimeoutMs: opts.requestTimeoutMs,
260
+ signal: opts.signal,
259
261
  });
260
262
  return snapshotInfo(record(payload.sandbox_checkpoint ?? payload.snapshot ?? payload));
261
263
  }
@@ -269,6 +271,7 @@ export class Sandbox {
269
271
  try {
270
272
  await control.delete(`/sandbox_snapshots/${snapshotId}`, {
271
273
  requestTimeoutMs: opts.requestTimeoutMs,
274
+ signal: opts.signal,
272
275
  });
273
276
  return true;
274
277
  }
@@ -279,8 +282,11 @@ export class Sandbox {
279
282
  }
280
283
  }
281
284
  /** Destroy this sandbox. */
282
- async kill() {
283
- await this.control.delete(`/sandboxes/${this.sandboxId}`);
285
+ async kill(opts = {}) {
286
+ await this.control.delete(`/sandboxes/${this.sandboxId}`, {
287
+ requestTimeoutMs: opts.requestTimeoutMs,
288
+ signal: opts.signal,
289
+ });
284
290
  return true;
285
291
  }
286
292
  /** Check if this sandbox is in a runtime-active lifecycle state. */
@@ -288,6 +294,7 @@ export class Sandbox {
288
294
  try {
289
295
  const payload = await this.control.get(`/sandboxes/${this.sandboxId}`, {
290
296
  requestTimeoutMs: opts.requestTimeoutMs,
297
+ signal: opts.signal,
291
298
  });
292
299
  const item = record(payload.sandbox ?? payload);
293
300
  return ['creating', 'ready', 'checkpointing', 'restoring', 'stopping'].includes(String(item.state ?? ''));
@@ -303,27 +310,33 @@ export class Sandbox {
303
310
  const control = new ControlClient(new ConnectionConfig(opts));
304
311
  await control.post(`/sandboxes/${sandboxId}/timeout`, {
305
312
  json: { timeout: Math.ceil(timeoutMs / 1000) },
313
+ requestTimeoutMs: opts.requestTimeoutMs,
314
+ signal: opts.signal,
306
315
  });
307
316
  }
308
317
  /** Set this sandbox's lifetime. */
309
- async setTimeout(timeoutMs) {
318
+ async setTimeout(timeoutMs, opts = {}) {
310
319
  await this.control.post(`/sandboxes/${this.sandboxId}/timeout`, {
311
320
  json: { timeout: Math.ceil(timeoutMs / 1000) },
321
+ requestTimeoutMs: opts.requestTimeoutMs,
322
+ signal: opts.signal,
312
323
  });
313
324
  }
314
- /** Keep the sandbox alive for `duration` milliseconds. */
315
- async keepAlive(duration) {
316
- await this.setTimeout(duration);
317
- }
318
325
  /** Fetch control-plane metadata for a sandbox by id. */
319
326
  static async getInfo(sandboxId, opts = {}) {
320
327
  const control = new ControlClient(new ConnectionConfig(opts));
321
- const payload = await control.get(`/sandboxes/${sandboxId}`);
328
+ const payload = await control.get(`/sandboxes/${sandboxId}`, {
329
+ requestTimeoutMs: opts.requestTimeoutMs,
330
+ signal: opts.signal,
331
+ });
322
332
  return sandboxInfo(record(payload.sandbox ?? payload));
323
333
  }
324
334
  /** Fetch the latest control-plane metadata for this sandbox. */
325
- async getInfo() {
326
- const payload = await this.control.get(`/sandboxes/${this.sandboxId}`);
335
+ async getInfo(opts = {}) {
336
+ const payload = await this.control.get(`/sandboxes/${this.sandboxId}`, {
337
+ requestTimeoutMs: opts.requestTimeoutMs,
338
+ signal: opts.signal,
339
+ });
327
340
  return sandboxInfo(record(payload.sandbox ?? payload));
328
341
  }
329
342
  /** Fetch latest sandbox metrics. */
@@ -355,13 +368,12 @@ export class Sandbox {
355
368
  if (checkpointId === undefined)
356
369
  throw new SandboxError('checkpointId or snapshotId is required');
357
370
  const payload = { checkpoint_id: checkpointId };
358
- if (restoreOpts.timeout !== undefined)
359
- payload.timeout_seconds = restoreOpts.timeout;
360
371
  if (restoreOpts.timeoutMs !== undefined)
361
372
  payload.timeout_seconds = Math.ceil(restoreOpts.timeoutMs / 1000);
362
373
  const response = await this.control.post(`/sandboxes/${this.sandboxId}/restore`, {
363
374
  json: payload,
364
375
  requestTimeoutMs: restoreOpts.requestTimeoutMs,
376
+ signal: restoreOpts.signal,
365
377
  });
366
378
  return sandboxInfo(record(response.sandbox ?? response));
367
379
  }
@@ -379,12 +391,6 @@ export class Sandbox {
379
391
  throw new SandboxError('port response did not include host or url');
380
392
  return `p${port}-${routeToken}.sandbox.${this.config.dataPlaneDomain}`;
381
393
  }
382
- /** Return the public hostname for the sandbox or an exposed sandbox port. */
383
- getHostname(port) {
384
- if (port !== undefined)
385
- return this.getHost(port);
386
- return new URL(this.dataPlane.baseUrl).host;
387
- }
388
394
  /** Return the conventional MCP URL for this sandbox. */
389
395
  getMcpUrl() {
390
396
  return `https://${this.getHost(this.mcpPort)}/mcp`;
@@ -404,12 +410,6 @@ export class Sandbox {
404
410
  throw error;
405
411
  }
406
412
  }
407
- /** Return a protocol string for a secure or insecure sandbox URL. */
408
- getProtocol(baseProtocol = 'http', secure = true) {
409
- return `${baseProtocol}${secure ? 's' : ''}`;
410
- }
411
- /** Close the local SDK attachment. This does not destroy the sandbox. */
412
- async close() { }
413
413
  /** Get a signed URL that accepts a POST upload for a sandbox file path. */
414
414
  async uploadUrl(path = '', opts = {}) {
415
415
  const fileUrl = await this.fileUrl('/upload_url', path, opts);
@@ -437,7 +437,7 @@ export class Sandbox {
437
437
  async betaPause(opts = {}) {
438
438
  return Sandbox.betaPause(this.sandboxId, { ...this.configOptions(), ...opts });
439
439
  }
440
- /** Alias for `betaPause`. */
440
+ /** Pause this sandbox. Returns false when it was already paused. */
441
441
  async pause(opts = {}) {
442
442
  return this.betaPause(opts);
443
443
  }
@@ -455,6 +455,7 @@ export class Sandbox {
455
455
  expires_in_seconds: opts.expiresInSeconds,
456
456
  }),
457
457
  requestTimeoutMs: opts.requestTimeoutMs,
458
+ signal: opts.signal,
458
459
  });
459
460
  return fileUrlInfo(record(payload.file_url ?? payload));
460
461
  }
@@ -463,33 +464,42 @@ export class Sandbox {
463
464
  return this.dataPlane.postJson(path, {
464
465
  json,
465
466
  requestTimeoutMs: opts.requestTimeoutMs,
467
+ signal: opts.signal,
466
468
  });
467
469
  }
468
470
  /** GET JSON from the sandbox data-plane runtime API. */
469
471
  async runtimeGetJson(path, opts = {}) {
470
472
  return this.dataPlane.getJson(path, {
471
473
  requestTimeoutMs: opts.requestTimeoutMs,
474
+ signal: opts.signal,
472
475
  });
473
476
  }
474
477
  /** DELETE JSON from the sandbox data-plane runtime API. */
475
478
  async runtimeDeleteJson(path, opts = {}) {
476
479
  return this.dataPlane.deleteJson(path, {
477
480
  requestTimeoutMs: opts.requestTimeoutMs,
481
+ signal: opts.signal,
478
482
  });
479
483
  }
480
484
  configOptions() {
481
485
  return {
482
486
  apiKey: this.config.apiKey,
483
487
  apiUrl: this.config.apiUrl,
488
+ sandboxUrl: this.config.sandboxUrl,
484
489
  dataPlaneDomain: this.config.dataPlaneDomain,
485
490
  requestTimeoutMs: this.config.requestTimeoutMs,
491
+ headers: this.config.headers,
492
+ apiHeaders: this.config.apiHeaders,
493
+ debug: this.config.debug,
494
+ signal: this.config.signal,
495
+ proxy: this.config.proxy,
486
496
  };
487
497
  }
488
498
  }
489
499
  function dataPlaneFromSession(session, config) {
490
500
  const item = record(session);
491
501
  const token = item.token ?? item.access_token;
492
- const url = item.data_plane_url;
502
+ const url = config.sandboxUrl ?? item.data_plane_url;
493
503
  if (!session)
494
504
  throw new SandboxError('sandbox session is required for data-plane operations');
495
505
  if (typeof token !== 'string' || typeof url !== 'string') {
@@ -499,8 +509,8 @@ function dataPlaneFromSession(session, config) {
499
509
  }
500
510
  function sandboxListPath(opts, nextToken) {
501
511
  const params = new URLSearchParams();
502
- if (opts.team)
503
- params.set('team', opts.team);
512
+ if (opts.team !== undefined)
513
+ params.set('team', String(opts.team));
504
514
  if (opts.limit !== undefined)
505
515
  params.set('limit', String(opts.limit));
506
516
  if (nextToken)
@@ -102,8 +102,6 @@ export declare class ReadyCmd {
102
102
  export declare function waitForPort(port: number): ReadyCmd;
103
103
  /** Return a ready check that waits for a URL to return an HTTP status code. */
104
104
  export declare function waitForURL(url: string, statusCode?: number): ReadyCmd;
105
- /** Alias for `waitForURL`. */
106
- export declare const waitForUrl: typeof waitForURL;
107
105
  /** Return a ready check that waits for a process name. */
108
106
  export declare function waitForProcess(processName: string): ReadyCmd;
109
107
  /** Return a ready check that waits for a file to exist. */
@@ -204,10 +202,13 @@ export declare class TemplateBase {
204
202
  }): TemplateBuilder;
205
203
  setStartCmd(startCommand: string, readyCommand: ReadyCommand): TemplateFinal;
206
204
  setReadyCmd(readyCommand: ReadyCommand): TemplateFinal;
205
+ betaDevContainerPrebuild(devcontainerDirectory: string): TemplateBuilder;
206
+ betaSetDevContainerStart(devcontainerDirectory: string): TemplateFinal;
207
207
  setEnvs(envs: Record<string, string>): TemplateBuilder;
208
208
  skipCache(): TemplateBuilder;
209
209
  toBuildSpec(): BuildSpec;
210
210
  private addPackages;
211
+ private requireDevContainerTemplate;
211
212
  private addCopySource;
212
213
  private addFileSpec;
213
214
  private resolveContextPath;
package/dist/template.js CHANGED
@@ -22,8 +22,6 @@ export function waitForPort(port) {
22
22
  export function waitForURL(url, statusCode = 200) {
23
23
  return new ReadyCmd(`curl -s -o /dev/null -w "%{http_code}" ${url} | grep -q "${statusCode}"`);
24
24
  }
25
- /** Alias for `waitForURL`. */
26
- export const waitForUrl = waitForURL;
27
25
  /** Return a ready check that waits for a process name. */
28
26
  export function waitForProcess(processName) {
29
27
  return new ReadyCmd(`pgrep ${processName} > /dev/null`);
@@ -286,6 +284,14 @@ export class TemplateBase {
286
284
  this.readyCmd = readyCommandText(readyCommand);
287
285
  return this;
288
286
  }
287
+ betaDevContainerPrebuild(devcontainerDirectory) {
288
+ this.requireDevContainerTemplate('betaDevContainerPrebuild');
289
+ return this.runCmd(`devcontainer build --workspace-folder ${devcontainerDirectory}`, { user: 'root' });
290
+ }
291
+ betaSetDevContainerStart(devcontainerDirectory) {
292
+ this.requireDevContainerTemplate('betaSetDevContainerStart');
293
+ return this.setStartCmd(`sudo devcontainer up --workspace-folder ${devcontainerDirectory} && sudo /prepare-exec.sh ${devcontainerDirectory} | sudo tee /devcontainer.sh > /dev/null && sudo chmod +x /devcontainer.sh && sudo touch /devcontainer.up`, waitForFile('/devcontainer.up'));
294
+ }
289
295
  setEnvs(envs) {
290
296
  Object.assign(this.env, envs);
291
297
  return this;
@@ -315,6 +321,11 @@ export class TemplateBase {
315
321
  addPackages(manager, packages) {
316
322
  this.packages[manager] = [...(this.packages[manager] ?? []), ...packages];
317
323
  }
324
+ requireDevContainerTemplate(method) {
325
+ if (this.base !== 'devcontainer') {
326
+ throw new SandboxError(`${method} can only be used with the devcontainer template`);
327
+ }
328
+ }
318
329
  addCopySource(source, dest, options, multipleSources) {
319
330
  const sourcePath = this.resolveContextPath(source);
320
331
  const stat = fs.statSync(sourcePath);
@@ -13,9 +13,8 @@ export type TerminalOpts = {
13
13
  terminalID?: string;
14
14
  cmd?: string;
15
15
  cwd?: string;
16
- rootDir?: string;
17
- envVars?: Record<string, string>;
18
- timeout?: number;
16
+ envs?: Record<string, string>;
17
+ timeoutMs?: number;
19
18
  };
20
19
  /** A running terminal session in a sandbox. */
21
20
  export declare class Terminal {
package/dist/terminal.js CHANGED
@@ -59,10 +59,9 @@ export class TerminalManager {
59
59
  const handle = await this.pty.create({
60
60
  cmd: opts.cmd,
61
61
  cwd: opts.cwd,
62
- rootDir: opts.rootDir,
63
- envVars: opts.envVars,
62
+ envs: opts.envs,
64
63
  size: opts.size,
65
- timeout: opts.timeout,
64
+ timeoutMs: opts.timeoutMs,
66
65
  onData: async (bytes) => {
67
66
  const data = new TextDecoder().decode(bytes);
68
67
  output.addData(data);
@@ -15,6 +15,7 @@ export declare class DataPlaneClient {
15
15
  readonly token: string;
16
16
  private readonly config;
17
17
  constructor(baseUrl: string, token: string, config: ConnectionConfig);
18
+ get headers(): Record<string, string>;
18
19
  getJson(path: string, opts?: RequestOpts): Promise<JsonRecord>;
19
20
  postJson(path: string, opts?: RequestOpts): Promise<JsonRecord>;
20
21
  deleteJson(path: string, opts?: RequestOpts): Promise<JsonRecord>;
@@ -29,6 +30,7 @@ export interface RequestOpts {
29
30
  body?: BodyInit | Uint8Array;
30
31
  headers?: Record<string, string>;
31
32
  requestTimeoutMs?: number;
33
+ signal?: AbortSignal;
32
34
  }
33
35
  export declare function withQuery(path: string, params: Record<string, string | number | boolean | undefined>): string;
34
36
  export {};
package/dist/transport.js CHANGED
@@ -28,9 +28,10 @@ export class ControlClient {
28
28
  headers: {
29
29
  ...this.config.authHeaders,
30
30
  ...(opts.json ? { 'content-type': 'application/json' } : {}),
31
+ ...(opts.headers ?? {}),
31
32
  },
32
33
  body: opts.json ? JSON.stringify(opts.json) : undefined,
33
- }, opts.requestTimeoutMs ?? this.config.requestTimeoutMs);
34
+ }, opts.requestTimeoutMs ?? this.config.requestTimeoutMs, opts.signal ?? this.config.signal);
34
35
  return parseJsonResponse(response);
35
36
  }
36
37
  }
@@ -43,6 +44,9 @@ export class DataPlaneClient {
43
44
  this.token = token;
44
45
  this.config = config;
45
46
  }
47
+ get headers() {
48
+ return this.config.headers;
49
+ }
46
50
  getJson(path, opts = {}) {
47
51
  return this.request(path, { ...opts, method: 'GET' });
48
52
  }
@@ -68,12 +72,13 @@ export class DataPlaneClient {
68
72
  const response = await fetchWithTimeout(joinUrl(this.baseUrl, path), {
69
73
  method: opts.method,
70
74
  headers: {
75
+ ...this.config.headers,
71
76
  Authorization: `Bearer ${this.token}`,
72
77
  ...(opts.json ? { 'content-type': 'application/json' } : {}),
73
78
  ...(opts.headers ?? {}),
74
79
  },
75
80
  body: opts.json ? JSON.stringify(opts.json) : opts.body,
76
- }, opts.requestTimeoutMs ?? this.config.requestTimeoutMs);
81
+ }, opts.requestTimeoutMs ?? this.config.requestTimeoutMs, opts.signal ?? this.config.signal);
77
82
  if (!response.ok) {
78
83
  throw errorFromResponse(response.status, await readJsonOrText(response));
79
84
  }
@@ -109,14 +114,26 @@ async function readJsonOrText(response) {
109
114
  return { message: text };
110
115
  }
111
116
  }
112
- function fetchWithTimeout(url, init, timeoutMs) {
117
+ function fetchWithTimeout(url, init, timeoutMs, signal) {
113
118
  const controller = new AbortController();
114
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
119
+ let timedOut = false;
120
+ const abortFromCaller = () => controller.abort();
121
+ if (signal?.aborted)
122
+ controller.abort();
123
+ else
124
+ signal?.addEventListener('abort', abortFromCaller, { once: true });
125
+ const timeout = setTimeout(() => {
126
+ timedOut = true;
127
+ controller.abort();
128
+ }, timeoutMs);
115
129
  return fetch(url, { ...init, signal: controller.signal }).catch((error) => {
116
- if (error?.name === 'AbortError')
130
+ if (error?.name === 'AbortError' && timedOut)
117
131
  throw new TimeoutError();
118
132
  throw error;
119
- }).finally(() => clearTimeout(timeout));
133
+ }).finally(() => {
134
+ clearTimeout(timeout);
135
+ signal?.removeEventListener('abort', abortFromCaller);
136
+ });
120
137
  }
121
138
  function joinUrl(base, path) {
122
139
  const normalizedBase = base.replace(/\/+$/, '');
package/dist/volume.d.ts CHANGED
@@ -83,8 +83,6 @@ export declare class Volume {
83
83
  static list(opts?: VolumeListOpts): Promise<VolumeInfo[]>;
84
84
  /** Destroy a volume by id or name. Returns false when it does not exist. */
85
85
  static destroy(volumeId: string, opts?: VolumeConnectionConfig): Promise<boolean>;
86
- /** Alias for `destroy`. */
87
- static delete(volumeId: string, opts?: VolumeConnectionConfig): Promise<boolean>;
88
86
  /** Fetch this volume's latest metadata. */
89
87
  getInfo(): Promise<VolumeInfo>;
90
88
  /** Fetch metadata for a path inside this volume. */
@@ -114,7 +112,5 @@ export declare class Volume {
114
112
  remove(path: string, opts?: ConnectionOpts): Promise<boolean>;
115
113
  /** Destroy this volume. Returns false when it no longer exists. */
116
114
  destroy(opts?: ConnectionOpts): Promise<boolean>;
117
- /** Alias for `destroy`. */
118
- delete(opts?: ConnectionOpts): Promise<boolean>;
119
115
  private configOptions;
120
116
  }