@watasu/sdk 0.1.25 → 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/README.md +50 -7
- package/dist/codeInterpreter.d.ts +33 -7
- package/dist/codeInterpreter.js +51 -10
- package/dist/commands.d.ts +5 -6
- package/dist/commands.js +6 -6
- package/dist/connectionConfig.d.ts +24 -0
- package/dist/connectionConfig.js +49 -2
- package/dist/filesystem.d.ts +7 -19
- package/dist/filesystem.js +1 -5
- package/dist/git.d.ts +1 -2
- package/dist/git.js +7 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/process.d.ts +1 -1
- package/dist/process.js +3 -3
- package/dist/processSocket.d.ts +2 -1
- package/dist/processSocket.js +4 -2
- package/dist/pty.d.ts +3 -3
- package/dist/pty.js +9 -7
- package/dist/sandbox.d.ts +40 -27
- package/dist/sandbox.js +130 -49
- package/dist/template.d.ts +3 -2
- package/dist/template.js +13 -2
- package/dist/terminal.d.ts +2 -3
- package/dist/terminal.js +2 -3
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +23 -6
- package/dist/volume.d.ts +116 -0
- package/dist/volume.js +278 -0
- package/package.json +1 -1
package/dist/sandbox.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Commands } from './commands.js';
|
|
2
2
|
import { ConnectionConfig, SESSION_OPERATION_REQUEST_TIMEOUT_MS } from './connectionConfig.js';
|
|
3
|
-
import { DataPlaneClient, ControlClient } from './transport.js';
|
|
3
|
+
import { DataPlaneClient, ControlClient, withQuery } from './transport.js';
|
|
4
4
|
import { ConflictError, FileNotFoundError, NotFoundError, SandboxError, unsupported } from './errors.js';
|
|
5
5
|
import { Filesystem } from './filesystem.js';
|
|
6
6
|
import { Git } from './git.js';
|
|
@@ -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);
|
|
@@ -123,8 +121,6 @@ export class Sandbox {
|
|
|
123
121
|
const template = typeof templateOrOpts === 'string'
|
|
124
122
|
? templateOrOpts
|
|
125
123
|
: templateOrOpts?.template ?? (sandboxOpts.mcp === undefined ? this.defaultTemplate : undefined);
|
|
126
|
-
if (sandboxOpts.volumeMounts !== undefined)
|
|
127
|
-
unsupported('volumeMounts');
|
|
128
124
|
const config = new ConnectionConfig(sandboxOpts);
|
|
129
125
|
const control = new ControlClient(config);
|
|
130
126
|
const sandboxPayload = {
|
|
@@ -136,11 +132,14 @@ export class Sandbox {
|
|
|
136
132
|
};
|
|
137
133
|
putIfPresent(sandboxPayload, 'template_id', template);
|
|
138
134
|
putIfPresent(sandboxPayload, 'mcp', sandboxOpts.mcp);
|
|
135
|
+
putIfPresent(sandboxPayload, 'lifecycle', lifecyclePayload(sandboxOpts.lifecycle));
|
|
136
|
+
putIfPresent(sandboxPayload, 'volume_mounts', volumeMountsPayload(sandboxOpts.volumeMounts));
|
|
139
137
|
Object.assign(sandboxPayload, networkUpdatePayload(sandboxOpts.network));
|
|
140
138
|
putIfPresent(sandboxPayload, 'team', sandboxOpts.team);
|
|
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,21 +215,26 @@ export class Sandbox {
|
|
|
216
215
|
throw error;
|
|
217
216
|
}
|
|
218
217
|
}
|
|
219
|
-
/**
|
|
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. */
|
|
230
233
|
static async getMetrics(sandboxId, opts = {}) {
|
|
231
234
|
const control = new ControlClient(new ConnectionConfig(opts));
|
|
232
|
-
const payload = await control.get(
|
|
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
|
-
/**
|
|
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,21 +464,42 @@ export class Sandbox {
|
|
|
463
464
|
return this.dataPlane.postJson(path, {
|
|
464
465
|
json,
|
|
465
466
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
467
|
+
signal: opts.signal,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
/** GET JSON from the sandbox data-plane runtime API. */
|
|
471
|
+
async runtimeGetJson(path, opts = {}) {
|
|
472
|
+
return this.dataPlane.getJson(path, {
|
|
473
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
474
|
+
signal: opts.signal,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
/** DELETE JSON from the sandbox data-plane runtime API. */
|
|
478
|
+
async runtimeDeleteJson(path, opts = {}) {
|
|
479
|
+
return this.dataPlane.deleteJson(path, {
|
|
480
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
481
|
+
signal: opts.signal,
|
|
466
482
|
});
|
|
467
483
|
}
|
|
468
484
|
configOptions() {
|
|
469
485
|
return {
|
|
470
486
|
apiKey: this.config.apiKey,
|
|
471
487
|
apiUrl: this.config.apiUrl,
|
|
488
|
+
sandboxUrl: this.config.sandboxUrl,
|
|
472
489
|
dataPlaneDomain: this.config.dataPlaneDomain,
|
|
473
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,
|
|
474
496
|
};
|
|
475
497
|
}
|
|
476
498
|
}
|
|
477
499
|
function dataPlaneFromSession(session, config) {
|
|
478
500
|
const item = record(session);
|
|
479
501
|
const token = item.token ?? item.access_token;
|
|
480
|
-
const url = item.data_plane_url;
|
|
502
|
+
const url = config.sandboxUrl ?? item.data_plane_url;
|
|
481
503
|
if (!session)
|
|
482
504
|
throw new SandboxError('sandbox session is required for data-plane operations');
|
|
483
505
|
if (typeof token !== 'string' || typeof url !== 'string') {
|
|
@@ -487,8 +509,8 @@ function dataPlaneFromSession(session, config) {
|
|
|
487
509
|
}
|
|
488
510
|
function sandboxListPath(opts, nextToken) {
|
|
489
511
|
const params = new URLSearchParams();
|
|
490
|
-
if (opts.team)
|
|
491
|
-
params.set('team', opts.team);
|
|
512
|
+
if (opts.team !== undefined)
|
|
513
|
+
params.set('team', String(opts.team));
|
|
492
514
|
if (opts.limit !== undefined)
|
|
493
515
|
params.set('limit', String(opts.limit));
|
|
494
516
|
if (nextToken)
|
|
@@ -515,6 +537,15 @@ function snapshotListPath(opts, nextToken) {
|
|
|
515
537
|
const query = params.toString();
|
|
516
538
|
return query ? `/sandbox_snapshots?${query}` : '/sandbox_snapshots';
|
|
517
539
|
}
|
|
540
|
+
function metricsPath(sandboxId, opts) {
|
|
541
|
+
return withQuery(`/sandboxes/${sandboxId}/metrics`, {
|
|
542
|
+
start: dateTimestampSeconds(opts.start),
|
|
543
|
+
end: dateTimestampSeconds(opts.end),
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
function dateTimestampSeconds(value) {
|
|
547
|
+
return value === undefined ? undefined : Math.round(value.getTime() / 1000);
|
|
548
|
+
}
|
|
518
549
|
function fileUrlInfo(payload) {
|
|
519
550
|
return {
|
|
520
551
|
method: String(payload.method ?? ''),
|
|
@@ -538,6 +569,8 @@ function sandboxInfo(payload) {
|
|
|
538
569
|
templateId: typeof payload.template_id === 'string' ? payload.template_id : templateSlug(payload.template),
|
|
539
570
|
name: typeof payload.name === 'string' ? payload.name : undefined,
|
|
540
571
|
state: typeof payload.state === 'string' ? payload.state : undefined,
|
|
572
|
+
lifecycle: sandboxLifecycleInfo(payload.lifecycle),
|
|
573
|
+
volumeMounts: volumeMountsInfo(payload.volume_mounts ?? payload.volumeMounts),
|
|
541
574
|
metadata: recordOfStrings(payload.metadata),
|
|
542
575
|
startedAt: typeof payload.started_at === 'string'
|
|
543
576
|
? payload.started_at
|
|
@@ -547,6 +580,45 @@ function sandboxInfo(payload) {
|
|
|
547
580
|
: typeof payload.deadline_at === 'string' ? payload.deadline_at : undefined,
|
|
548
581
|
};
|
|
549
582
|
}
|
|
583
|
+
function lifecyclePayload(lifecycle) {
|
|
584
|
+
if (lifecycle === undefined)
|
|
585
|
+
return undefined;
|
|
586
|
+
const onTimeout = lifecycle.onTimeout ?? 'kill';
|
|
587
|
+
const autoResume = lifecycle.autoResume ?? false;
|
|
588
|
+
if (autoResume && onTimeout !== 'pause') {
|
|
589
|
+
throw new SandboxError("lifecycle.autoResume can only be true when lifecycle.onTimeout is 'pause'");
|
|
590
|
+
}
|
|
591
|
+
return { on_timeout: onTimeout, auto_resume: autoResume };
|
|
592
|
+
}
|
|
593
|
+
function volumeMountsPayload(volumeMounts) {
|
|
594
|
+
if (volumeMounts === undefined)
|
|
595
|
+
return undefined;
|
|
596
|
+
return Object.entries(volumeMounts).map(([path, volume]) => ({
|
|
597
|
+
path,
|
|
598
|
+
name: typeof volume === 'string' ? volume : volume.name,
|
|
599
|
+
}));
|
|
600
|
+
}
|
|
601
|
+
function volumeMountsInfo(value) {
|
|
602
|
+
if (!Array.isArray(value))
|
|
603
|
+
return undefined;
|
|
604
|
+
return value
|
|
605
|
+
.map((item) => {
|
|
606
|
+
const entry = record(item);
|
|
607
|
+
return { name: String(entry.name ?? ''), path: String(entry.path ?? '') };
|
|
608
|
+
})
|
|
609
|
+
.filter((entry) => entry.name !== '' && entry.path !== '');
|
|
610
|
+
}
|
|
611
|
+
function sandboxLifecycleInfo(value) {
|
|
612
|
+
const lifecycle = record(value);
|
|
613
|
+
const onTimeout = stringValue(lifecycle.on_timeout ?? lifecycle.onTimeout);
|
|
614
|
+
const autoResume = booleanValue(lifecycle.auto_resume ?? lifecycle.autoResume);
|
|
615
|
+
if (onTimeout === undefined && autoResume === undefined)
|
|
616
|
+
return undefined;
|
|
617
|
+
return {
|
|
618
|
+
onTimeout: onTimeout ?? 'kill',
|
|
619
|
+
autoResume: autoResume ?? false,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
550
622
|
function metricsList(value) {
|
|
551
623
|
if (Array.isArray(value))
|
|
552
624
|
return value.map((item) => metricsInfo(record(item)));
|
|
@@ -596,6 +668,15 @@ function stringValue(value) {
|
|
|
596
668
|
function numberValue(value) {
|
|
597
669
|
return typeof value === 'number' ? value : undefined;
|
|
598
670
|
}
|
|
671
|
+
function booleanValue(value) {
|
|
672
|
+
if (typeof value === 'boolean')
|
|
673
|
+
return value;
|
|
674
|
+
if (value === 'true' || value === '1')
|
|
675
|
+
return true;
|
|
676
|
+
if (value === 'false' || value === '0')
|
|
677
|
+
return false;
|
|
678
|
+
return undefined;
|
|
679
|
+
}
|
|
599
680
|
function templateSlug(value) {
|
|
600
681
|
const template = record(value);
|
|
601
682
|
return typeof template.slug === 'string' ? template.slug : undefined;
|
package/dist/template.d.ts
CHANGED
|
@@ -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);
|
package/dist/terminal.d.ts
CHANGED
|
@@ -13,9 +13,8 @@ export type TerminalOpts = {
|
|
|
13
13
|
terminalID?: string;
|
|
14
14
|
cmd?: string;
|
|
15
15
|
cwd?: string;
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
63
|
-
envVars: opts.envVars,
|
|
62
|
+
envs: opts.envs,
|
|
64
63
|
size: opts.size,
|
|
65
|
-
|
|
64
|
+
timeoutMs: opts.timeoutMs,
|
|
66
65
|
onData: async (bytes) => {
|
|
67
66
|
const data = new TextDecoder().decode(bytes);
|
|
68
67
|
output.addData(data);
|
package/dist/transport.d.ts
CHANGED
|
@@ -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
|
-
|
|
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(() =>
|
|
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(/\/+$/, '');
|