@watasu/sdk 0.1.5 → 0.1.24
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 +145 -2
- package/dist/commands.d.ts +33 -4
- package/dist/commands.js +68 -17
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +8 -0
- package/dist/filesystem.d.ts +80 -13
- package/dist/filesystem.js +184 -9
- package/dist/git.d.ts +171 -0
- package/dist/git.js +277 -0
- package/dist/index.d.ts +16 -6
- package/dist/index.js +9 -4
- package/dist/process.d.ts +56 -0
- package/dist/process.js +137 -0
- package/dist/processSocket.d.ts +2 -0
- package/dist/processSocket.js +9 -1
- package/dist/pty.d.ts +37 -0
- package/dist/pty.js +90 -0
- package/dist/sandbox.d.ts +145 -25
- package/dist/sandbox.js +312 -45
- package/dist/template.d.ts +232 -0
- package/dist/template.js +624 -0
- package/dist/terminal.d.ts +41 -0
- package/dist/terminal.js +74 -0
- package/dist/transport.d.ts +1 -0
- package/dist/transport.js +3 -0
- package/package.json +1 -1
package/dist/sandbox.js
CHANGED
|
@@ -1,33 +1,96 @@
|
|
|
1
1
|
import { Commands } from './commands.js';
|
|
2
2
|
import { ConnectionConfig, SESSION_OPERATION_REQUEST_TIMEOUT_MS } from './connectionConfig.js';
|
|
3
3
|
import { DataPlaneClient, ControlClient } from './transport.js';
|
|
4
|
-
import { NotFoundError, SandboxError, unsupported } from './errors.js';
|
|
4
|
+
import { ConflictError, FileNotFoundError, NotFoundError, SandboxError, unsupported } from './errors.js';
|
|
5
5
|
import { Filesystem } from './filesystem.js';
|
|
6
|
+
import { Git } from './git.js';
|
|
7
|
+
import { Pty } from './pty.js';
|
|
8
|
+
import { ProcessManager } from './process.js';
|
|
9
|
+
import { TerminalManager } from './terminal.js';
|
|
10
|
+
/** Paginator for listing sandbox snapshots. */
|
|
6
11
|
export class SnapshotPaginator {
|
|
7
|
-
|
|
8
|
-
consumed = false;
|
|
12
|
+
opts;
|
|
9
13
|
hasNext = true;
|
|
10
14
|
nextToken;
|
|
11
|
-
constructor(
|
|
12
|
-
this.
|
|
15
|
+
constructor(opts = {}) {
|
|
16
|
+
this.opts = opts;
|
|
17
|
+
this.nextToken = opts.nextToken;
|
|
13
18
|
}
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
/** Fetch the next page of snapshot metadata. */
|
|
20
|
+
async nextItems(opts = {}) {
|
|
21
|
+
if (!this.hasNext)
|
|
16
22
|
throw new SandboxError('No more snapshots to fetch');
|
|
17
|
-
this.
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
const config = new ConnectionConfig({ ...this.opts, ...opts });
|
|
24
|
+
const control = new ControlClient(config);
|
|
25
|
+
const payload = await control.get(snapshotListPath(this.opts, this.nextToken), {
|
|
26
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
27
|
+
});
|
|
28
|
+
this.nextToken = stringValue(payload.next_token ?? payload.nextToken);
|
|
29
|
+
this.hasNext = this.nextToken !== undefined;
|
|
30
|
+
const snapshots = Array.isArray(payload.snapshots)
|
|
31
|
+
? payload.snapshots
|
|
32
|
+
: Array.isArray(payload.sandbox_checkpoints) ? payload.sandbox_checkpoints : [];
|
|
33
|
+
return snapshots.map((item) => snapshotInfo(record(item)));
|
|
34
|
+
}
|
|
35
|
+
/** Drain all remaining pages into one list. */
|
|
36
|
+
async listItems(opts = {}) {
|
|
37
|
+
const items = [];
|
|
38
|
+
while (this.hasNext)
|
|
39
|
+
items.push(...await this.nextItems(opts));
|
|
40
|
+
return items;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Paginator for listing sandboxes. */
|
|
44
|
+
export class SandboxPaginator {
|
|
45
|
+
opts;
|
|
46
|
+
hasNext = true;
|
|
47
|
+
nextToken;
|
|
48
|
+
constructor(opts = {}) {
|
|
49
|
+
this.opts = opts;
|
|
50
|
+
this.nextToken = opts.nextToken;
|
|
51
|
+
}
|
|
52
|
+
/** Fetch the next page of sandbox metadata. */
|
|
53
|
+
async nextItems(opts = {}) {
|
|
54
|
+
if (!this.hasNext)
|
|
55
|
+
throw new SandboxError('No more sandboxes to fetch');
|
|
56
|
+
const config = new ConnectionConfig({ ...this.opts, ...opts });
|
|
57
|
+
const control = new ControlClient(config);
|
|
58
|
+
const payload = await control.get(sandboxListPath(this.opts, this.nextToken), {
|
|
59
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
60
|
+
});
|
|
61
|
+
this.nextToken = stringValue(payload.next_token ?? payload.nextToken);
|
|
62
|
+
this.hasNext = this.nextToken !== undefined;
|
|
63
|
+
const sandboxes = Array.isArray(payload.sandboxes) ? payload.sandboxes : [];
|
|
64
|
+
return sandboxes.map((item) => sandboxInfo(record(item)));
|
|
65
|
+
}
|
|
66
|
+
/** Drain all remaining pages into one list. */
|
|
67
|
+
async listItems(opts = {}) {
|
|
68
|
+
const items = [];
|
|
69
|
+
while (this.hasNext)
|
|
70
|
+
items.push(...await this.nextItems(opts));
|
|
71
|
+
return items;
|
|
20
72
|
}
|
|
21
73
|
}
|
|
22
74
|
/** Running Watasu sandbox with ready `files` and `commands` helpers. */
|
|
23
75
|
export class Sandbox {
|
|
24
76
|
/** Default template slug used when create is called without a template. */
|
|
25
77
|
static defaultTemplate = 'base';
|
|
78
|
+
/** Default template slug used by MCP creation once Watasu supports it. */
|
|
79
|
+
static defaultMcpTemplate = 'mcp-gateway';
|
|
80
|
+
/** Default sandbox lifetime in milliseconds. */
|
|
81
|
+
static defaultSandboxTimeoutMs = 300_000;
|
|
26
82
|
files;
|
|
83
|
+
filesystem;
|
|
27
84
|
commands;
|
|
85
|
+
process;
|
|
86
|
+
pty;
|
|
87
|
+
terminal;
|
|
88
|
+
git;
|
|
89
|
+
cwd;
|
|
90
|
+
envVars;
|
|
28
91
|
sandboxId;
|
|
29
|
-
|
|
30
|
-
|
|
92
|
+
mcpPort = 50005;
|
|
93
|
+
mcpToken;
|
|
31
94
|
config;
|
|
32
95
|
control;
|
|
33
96
|
envs;
|
|
@@ -38,32 +101,42 @@ export class Sandbox {
|
|
|
38
101
|
this.config = opts.connectionConfig;
|
|
39
102
|
this.control = opts.control ?? new ControlClient(this.config);
|
|
40
103
|
this.envs = opts.envs ?? {};
|
|
104
|
+
this.envVars = this.envs;
|
|
41
105
|
this.sandbox = opts.sandbox ?? {};
|
|
42
106
|
const dataPlane = dataPlaneFromSession(opts.session, this.config);
|
|
43
107
|
this.dataPlane = dataPlane;
|
|
44
108
|
this.files = new Filesystem(dataPlane);
|
|
109
|
+
this.filesystem = this.files;
|
|
45
110
|
this.commands = new Commands(dataPlane, this.config, this.envs);
|
|
111
|
+
this.process = new ProcessManager(this.commands);
|
|
112
|
+
this.pty = new Pty(dataPlane, this.config);
|
|
113
|
+
this.terminal = new TerminalManager(this.pty);
|
|
114
|
+
this.git = new Git(dataPlane);
|
|
115
|
+
}
|
|
116
|
+
/** Sandbox id alias used by SDK-compatible code. */
|
|
117
|
+
get id() {
|
|
118
|
+
return this.sandboxId;
|
|
46
119
|
}
|
|
47
120
|
/** Create a sandbox and return it only after the API supplies a data-plane session. */
|
|
48
121
|
static async create(templateOrOpts, opts = {}) {
|
|
122
|
+
const sandboxOpts = typeof templateOrOpts === 'string' ? opts : templateOrOpts ?? {};
|
|
49
123
|
const template = typeof templateOrOpts === 'string'
|
|
50
124
|
? templateOrOpts
|
|
51
|
-
: templateOrOpts?.template ?? Sandbox.defaultTemplate;
|
|
52
|
-
const sandboxOpts = typeof templateOrOpts === 'string' ? opts : templateOrOpts ?? {};
|
|
53
|
-
if (sandboxOpts.mcp !== undefined)
|
|
54
|
-
unsupported('mcp');
|
|
125
|
+
: templateOrOpts?.template ?? (sandboxOpts.mcp === undefined ? Sandbox.defaultTemplate : undefined);
|
|
55
126
|
if (sandboxOpts.volumeMounts !== undefined)
|
|
56
127
|
unsupported('volumeMounts');
|
|
57
128
|
const config = new ConnectionConfig(sandboxOpts);
|
|
58
129
|
const control = new ControlClient(config);
|
|
59
130
|
const sandboxPayload = {
|
|
60
|
-
template_id: template,
|
|
61
131
|
timeout: Math.ceil((sandboxOpts.timeoutMs ?? 300_000) / 1000),
|
|
62
132
|
metadata: sandboxOpts.metadata ?? {},
|
|
63
133
|
env_vars: sandboxOpts.envs ?? {},
|
|
64
134
|
secure: sandboxOpts.secure ?? true,
|
|
65
135
|
allow_internet_access: sandboxOpts.allowInternetAccess ?? true,
|
|
66
136
|
};
|
|
137
|
+
putIfPresent(sandboxPayload, 'template_id', template);
|
|
138
|
+
putIfPresent(sandboxPayload, 'mcp', sandboxOpts.mcp);
|
|
139
|
+
Object.assign(sandboxPayload, networkUpdatePayload(sandboxOpts.network));
|
|
67
140
|
putIfPresent(sandboxPayload, 'team', sandboxOpts.team);
|
|
68
141
|
const response = await control.post('/sandboxes', {
|
|
69
142
|
json: sandboxPayload,
|
|
@@ -73,7 +146,7 @@ export class Sandbox {
|
|
|
73
146
|
const sandboxId = sandbox.id ?? sandbox.sandbox_id;
|
|
74
147
|
if (sandboxId === undefined)
|
|
75
148
|
throw new SandboxError('create response did not include sandbox id');
|
|
76
|
-
|
|
149
|
+
const sandboxInstance = new Sandbox({
|
|
77
150
|
sandboxId: String(sandboxId),
|
|
78
151
|
connectionConfig: config,
|
|
79
152
|
control,
|
|
@@ -81,13 +154,14 @@ export class Sandbox {
|
|
|
81
154
|
sandbox,
|
|
82
155
|
envs: sandboxOpts.envs,
|
|
83
156
|
});
|
|
157
|
+
return sandboxInstance;
|
|
84
158
|
}
|
|
85
159
|
/** Connect to an existing sandbox and return it with a fresh data-plane session. */
|
|
86
160
|
static async connect(sandboxId, opts = {}) {
|
|
87
161
|
const config = new ConnectionConfig(opts);
|
|
88
162
|
const control = new ControlClient(config);
|
|
89
163
|
const info = await control.get(`/sandboxes/${sandboxId}`);
|
|
90
|
-
const response = await control.post(`/sandboxes/${sandboxId}/
|
|
164
|
+
const response = await control.post(`/sandboxes/${sandboxId}/resume`, {
|
|
91
165
|
json: opts.timeoutMs ? { timeout: Math.ceil(opts.timeoutMs / 1000) } : {},
|
|
92
166
|
requestTimeoutMs: sessionOperationRequestTimeout(config, opts),
|
|
93
167
|
});
|
|
@@ -99,9 +173,14 @@ export class Sandbox {
|
|
|
99
173
|
sandbox: record(response.sandbox ?? info.sandbox ?? {}),
|
|
100
174
|
});
|
|
101
175
|
}
|
|
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
|
+
}
|
|
102
181
|
/** Refresh this sandbox's data-plane session in place. */
|
|
103
182
|
async connect(opts = {}) {
|
|
104
|
-
const response = await this.control.post(`/sandboxes/${this.sandboxId}/
|
|
183
|
+
const response = await this.control.post(`/sandboxes/${this.sandboxId}/resume`, {
|
|
105
184
|
json: opts.timeoutMs ? { timeout: Math.ceil(opts.timeoutMs / 1000) } : {},
|
|
106
185
|
requestTimeoutMs: sessionOperationRequestTimeout(this.config, opts),
|
|
107
186
|
});
|
|
@@ -109,12 +188,41 @@ export class Sandbox {
|
|
|
109
188
|
const dataPlane = dataPlaneFromSession(response.session, this.config);
|
|
110
189
|
this.dataPlane = dataPlane;
|
|
111
190
|
this.files = new Filesystem(dataPlane);
|
|
191
|
+
this.filesystem = this.files;
|
|
112
192
|
this.commands = new Commands(dataPlane, this.config, this.envs);
|
|
193
|
+
this.process = new ProcessManager(this.commands);
|
|
194
|
+
this.pty = new Pty(dataPlane, this.config);
|
|
195
|
+
this.terminal = new TerminalManager(this.pty);
|
|
196
|
+
this.git = new Git(dataPlane);
|
|
113
197
|
return this;
|
|
114
198
|
}
|
|
199
|
+
/** Resume a paused sandbox by id. */
|
|
200
|
+
static async resume(sandboxId, opts = {}) {
|
|
201
|
+
await Sandbox.connect(sandboxId, opts);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
/** Pause a sandbox by id. Returns false when it was already paused. */
|
|
205
|
+
static async betaPause(sandboxId, opts = {}) {
|
|
206
|
+
const control = new ControlClient(new ConnectionConfig(opts));
|
|
207
|
+
try {
|
|
208
|
+
await control.post(`/sandboxes/${sandboxId}/pause`, {
|
|
209
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
210
|
+
});
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (error instanceof ConflictError)
|
|
215
|
+
return false;
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** Alias for `betaPause`. */
|
|
220
|
+
static async pause(sandboxId, opts = {}) {
|
|
221
|
+
return this.betaPause(sandboxId, opts);
|
|
222
|
+
}
|
|
115
223
|
/** Destroy a sandbox by id. */
|
|
116
224
|
static async kill(sandboxId, opts = {}) {
|
|
117
|
-
const control = new ControlClient(new ConnectionConfig(opts));
|
|
225
|
+
const control = new ControlClient(new ConnectionConfig(typeof opts === 'string' ? { apiKey: opts } : opts));
|
|
118
226
|
await control.delete(`/sandboxes/${sandboxId}`);
|
|
119
227
|
return true;
|
|
120
228
|
}
|
|
@@ -126,6 +234,18 @@ export class Sandbox {
|
|
|
126
234
|
});
|
|
127
235
|
return metricsList(payload.metrics ?? payload);
|
|
128
236
|
}
|
|
237
|
+
/** Atomically replace a sandbox's network egress policy by id. */
|
|
238
|
+
static async updateNetwork(sandboxId, network, opts = {}) {
|
|
239
|
+
await this.putNetwork(sandboxId, network, opts);
|
|
240
|
+
}
|
|
241
|
+
static async putNetwork(sandboxId, network, opts = {}) {
|
|
242
|
+
const control = new ControlClient(new ConnectionConfig(opts));
|
|
243
|
+
const response = await control.put(`/sandboxes/${sandboxId}/network`, {
|
|
244
|
+
json: networkUpdatePayload(network),
|
|
245
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
246
|
+
});
|
|
247
|
+
return response.sandbox === undefined ? undefined : record(response.sandbox);
|
|
248
|
+
}
|
|
129
249
|
/** Deprecated alias for `getInfo`. */
|
|
130
250
|
static async getFullInfo(sandboxId, opts = {}) {
|
|
131
251
|
return this.getInfo(sandboxId, opts);
|
|
@@ -133,26 +253,30 @@ export class Sandbox {
|
|
|
133
253
|
/** Create a Watasu checkpoint using snapshot naming. */
|
|
134
254
|
static async createSnapshot(sandboxId, opts = {}) {
|
|
135
255
|
const control = new ControlClient(new ConnectionConfig(opts));
|
|
136
|
-
const payload = await control.post(`/sandboxes/${sandboxId}/
|
|
256
|
+
const payload = await control.post(`/sandboxes/${sandboxId}/snapshots`, {
|
|
137
257
|
json: snapshotPayload(opts),
|
|
138
258
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
139
259
|
});
|
|
140
260
|
return snapshotInfo(record(payload.sandbox_checkpoint ?? payload.snapshot ?? payload));
|
|
141
261
|
}
|
|
142
|
-
/** List
|
|
143
|
-
static listSnapshots(
|
|
144
|
-
return new SnapshotPaginator(
|
|
145
|
-
|
|
146
|
-
|
|
262
|
+
/** List snapshots visible to the configured API key. */
|
|
263
|
+
static listSnapshots(opts = {}) {
|
|
264
|
+
return new SnapshotPaginator(opts);
|
|
265
|
+
}
|
|
266
|
+
/** Delete a snapshot by id. Returns `false` when the snapshot does not exist. */
|
|
267
|
+
static async deleteSnapshot(snapshotId, opts = {}) {
|
|
268
|
+
const control = new ControlClient(new ConnectionConfig(opts));
|
|
269
|
+
try {
|
|
270
|
+
await control.delete(`/sandbox_snapshots/${snapshotId}`, {
|
|
147
271
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
148
272
|
});
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (error instanceof NotFoundError)
|
|
277
|
+
return false;
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
156
280
|
}
|
|
157
281
|
/** Destroy this sandbox. */
|
|
158
282
|
async kill() {
|
|
@@ -187,6 +311,10 @@ export class Sandbox {
|
|
|
187
311
|
json: { timeout: Math.ceil(timeoutMs / 1000) },
|
|
188
312
|
});
|
|
189
313
|
}
|
|
314
|
+
/** Keep the sandbox alive for `duration` milliseconds. */
|
|
315
|
+
async keepAlive(duration) {
|
|
316
|
+
await this.setTimeout(duration);
|
|
317
|
+
}
|
|
190
318
|
/** Fetch control-plane metadata for a sandbox by id. */
|
|
191
319
|
static async getInfo(sandboxId, opts = {}) {
|
|
192
320
|
const control = new ControlClient(new ConnectionConfig(opts));
|
|
@@ -206,13 +334,17 @@ export class Sandbox {
|
|
|
206
334
|
async createSnapshot(opts = {}) {
|
|
207
335
|
return Sandbox.createSnapshot(this.sandboxId, { ...this.configOptions(), ...opts });
|
|
208
336
|
}
|
|
337
|
+
/** Delete a snapshot by id. */
|
|
338
|
+
async deleteSnapshot(snapshotId, opts = {}) {
|
|
339
|
+
return Sandbox.deleteSnapshot(snapshotId, { ...this.configOptions(), ...opts });
|
|
340
|
+
}
|
|
209
341
|
/** Watasu-native alias for `createSnapshot`. */
|
|
210
342
|
async checkpoint(opts = {}) {
|
|
211
343
|
return this.createSnapshot(opts);
|
|
212
344
|
}
|
|
213
345
|
/** List checkpoints for this sandbox using snapshot naming. */
|
|
214
346
|
listSnapshots(opts = {}) {
|
|
215
|
-
return Sandbox.listSnapshots(
|
|
347
|
+
return Sandbox.listSnapshots({ ...this.configOptions(), ...opts, sandboxId: this.sandboxId });
|
|
216
348
|
}
|
|
217
349
|
/** Restore a checkpoint into a new sandbox and return its control-plane info. */
|
|
218
350
|
async restore(opts = {}) {
|
|
@@ -233,12 +365,10 @@ export class Sandbox {
|
|
|
233
365
|
});
|
|
234
366
|
return sandboxInfo(record(response.sandbox ?? response));
|
|
235
367
|
}
|
|
236
|
-
/**
|
|
237
|
-
static
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
const sandboxes = Array.isArray(payload.sandboxes) ? payload.sandboxes : [];
|
|
241
|
-
return sandboxes.map((item) => sandboxInfo(record(item)));
|
|
368
|
+
/** Return a paginator for sandboxes visible to the configured API key. */
|
|
369
|
+
static list(opts = {}) {
|
|
370
|
+
const listOpts = typeof opts === 'string' ? { apiKey: opts } : opts;
|
|
371
|
+
return new SandboxPaginator(listOpts);
|
|
242
372
|
}
|
|
243
373
|
/** Return the public hostname for an exposed sandbox port. */
|
|
244
374
|
getHost(port) {
|
|
@@ -249,10 +379,85 @@ export class Sandbox {
|
|
|
249
379
|
throw new SandboxError('port response did not include host or url');
|
|
250
380
|
return `p${port}-${routeToken}.sandbox.${this.config.dataPlaneDomain}`;
|
|
251
381
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
+
/** Return the conventional MCP URL for this sandbox. */
|
|
389
|
+
getMcpUrl() {
|
|
390
|
+
return `https://${this.getHost(this.mcpPort)}/mcp`;
|
|
391
|
+
}
|
|
392
|
+
/** Return the MCP gateway token when the sandbox contains one. */
|
|
393
|
+
async getMcpToken() {
|
|
394
|
+
if (this.mcpToken !== undefined)
|
|
395
|
+
return this.mcpToken;
|
|
396
|
+
try {
|
|
397
|
+
const token = await this.files.read('/etc/mcp-gateway/.token', { user: 'root' });
|
|
398
|
+
this.mcpToken = String(token).trim() || undefined;
|
|
399
|
+
return this.mcpToken;
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
if (error instanceof FileNotFoundError || error instanceof NotFoundError)
|
|
403
|
+
return undefined;
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
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
|
+
/** Get a signed URL that accepts a POST upload for a sandbox file path. */
|
|
414
|
+
async uploadUrl(path = '', opts = {}) {
|
|
415
|
+
const fileUrl = await this.fileUrl('/upload_url', path, opts);
|
|
416
|
+
return fileUrl.url;
|
|
417
|
+
}
|
|
418
|
+
/** Get a signed URL that accepts a GET download for a sandbox file path. */
|
|
419
|
+
async downloadUrl(path, opts = {}) {
|
|
420
|
+
const fileUrl = await this.fileUrl('/download_url', path, opts);
|
|
421
|
+
return fileUrl.url;
|
|
422
|
+
}
|
|
423
|
+
/** Get signed upload URL metadata for a sandbox file path. */
|
|
424
|
+
async uploadUrlInfo(path = '', opts = {}) {
|
|
425
|
+
return this.fileUrl('/upload_url', path, opts);
|
|
426
|
+
}
|
|
427
|
+
/** Get signed download URL metadata for a sandbox file path. */
|
|
428
|
+
async downloadUrlInfo(path, opts = {}) {
|
|
429
|
+
return this.fileUrl('/download_url', path, opts);
|
|
430
|
+
}
|
|
431
|
+
/** Atomically replace this sandbox's network egress policy. */
|
|
432
|
+
async updateNetwork(network, opts = {}) {
|
|
433
|
+
const sandbox = await Sandbox.putNetwork(this.sandboxId, network, { ...this.configOptions(), ...opts });
|
|
434
|
+
this.sandbox = sandbox ?? this.sandbox;
|
|
435
|
+
}
|
|
436
|
+
/** Pause this sandbox. Returns false when it was already paused. */
|
|
437
|
+
async betaPause(opts = {}) {
|
|
438
|
+
return Sandbox.betaPause(this.sandboxId, { ...this.configOptions(), ...opts });
|
|
439
|
+
}
|
|
440
|
+
/** Alias for `betaPause`. */
|
|
441
|
+
async pause(opts = {}) {
|
|
442
|
+
return this.betaPause(opts);
|
|
443
|
+
}
|
|
444
|
+
/** Resume this sandbox and refresh its data-plane session. */
|
|
445
|
+
async resume(opts = {}) {
|
|
446
|
+
await this.connect(opts);
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
async fileUrl(route, path, opts) {
|
|
450
|
+
const payload = await this.control.post(`/sandboxes/${this.sandboxId}/files${route}`, {
|
|
451
|
+
json: compactRecord({
|
|
452
|
+
path,
|
|
453
|
+
user: opts.user,
|
|
454
|
+
use_signature_expiration: opts.useSignatureExpiration,
|
|
455
|
+
expires_in_seconds: opts.expiresInSeconds,
|
|
456
|
+
}),
|
|
457
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
458
|
+
});
|
|
459
|
+
return fileUrlInfo(record(payload.file_url ?? payload));
|
|
460
|
+
}
|
|
256
461
|
configOptions() {
|
|
257
462
|
return {
|
|
258
463
|
apiKey: this.config.apiKey,
|
|
@@ -273,6 +478,48 @@ function dataPlaneFromSession(session, config) {
|
|
|
273
478
|
}
|
|
274
479
|
return new DataPlaneClient(url, token, config);
|
|
275
480
|
}
|
|
481
|
+
function sandboxListPath(opts, nextToken) {
|
|
482
|
+
const params = new URLSearchParams();
|
|
483
|
+
if (opts.team)
|
|
484
|
+
params.set('team', opts.team);
|
|
485
|
+
if (opts.limit !== undefined)
|
|
486
|
+
params.set('limit', String(opts.limit));
|
|
487
|
+
if (nextToken)
|
|
488
|
+
params.set('next_token', nextToken);
|
|
489
|
+
if (opts.query?.metadata) {
|
|
490
|
+
for (const [key, value] of Object.entries(opts.query.metadata)) {
|
|
491
|
+
params.append(`query[metadata][${key}]`, value);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
for (const state of opts.query?.state ?? []) {
|
|
495
|
+
params.append('query[state][]', state);
|
|
496
|
+
}
|
|
497
|
+
const query = params.toString();
|
|
498
|
+
return query ? `/sandboxes?${query}` : '/sandboxes';
|
|
499
|
+
}
|
|
500
|
+
function snapshotListPath(opts, nextToken) {
|
|
501
|
+
const params = new URLSearchParams();
|
|
502
|
+
if (opts.sandboxId)
|
|
503
|
+
params.set('sandbox_id', opts.sandboxId);
|
|
504
|
+
if (opts.limit !== undefined)
|
|
505
|
+
params.set('limit', String(opts.limit));
|
|
506
|
+
if (nextToken)
|
|
507
|
+
params.set('next_token', nextToken);
|
|
508
|
+
const query = params.toString();
|
|
509
|
+
return query ? `/sandbox_snapshots?${query}` : '/sandbox_snapshots';
|
|
510
|
+
}
|
|
511
|
+
function fileUrlInfo(payload) {
|
|
512
|
+
return {
|
|
513
|
+
method: String(payload.method ?? ''),
|
|
514
|
+
path: String(payload.path ?? ''),
|
|
515
|
+
url: String(payload.url ?? ''),
|
|
516
|
+
expiresAt: typeof payload.expires_at === 'string' ? payload.expires_at : typeof payload.expiresAt === 'string' ? payload.expiresAt : undefined,
|
|
517
|
+
raw: payload,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function compactRecord(payload) {
|
|
521
|
+
return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== undefined));
|
|
522
|
+
}
|
|
276
523
|
function sessionOperationRequestTimeout(config, opts) {
|
|
277
524
|
if (opts.requestTimeoutMs !== undefined)
|
|
278
525
|
return opts.requestTimeoutMs;
|
|
@@ -350,6 +597,26 @@ function putIfPresent(target, key, value) {
|
|
|
350
597
|
if (value !== undefined && value !== null)
|
|
351
598
|
target[key] = value;
|
|
352
599
|
}
|
|
600
|
+
function networkUpdatePayload(network) {
|
|
601
|
+
if (network === undefined)
|
|
602
|
+
return {};
|
|
603
|
+
if (typeof network !== 'object' || network === null)
|
|
604
|
+
unsupported('network callable rules');
|
|
605
|
+
if (network.rules !== undefined)
|
|
606
|
+
unsupported('network rules');
|
|
607
|
+
if (network.maskRequestHost !== undefined)
|
|
608
|
+
unsupported('network request host masking');
|
|
609
|
+
return compactRecord({
|
|
610
|
+
allow_out: network.allowOut,
|
|
611
|
+
deny_out: network.denyOut,
|
|
612
|
+
allow_internet_access: network.allowInternetAccess,
|
|
613
|
+
allow_package_registry_access: network.allowPackageRegistryAccess,
|
|
614
|
+
allow_public_traffic: network.allowPublicTraffic,
|
|
615
|
+
egress_profile: network.egressProfile,
|
|
616
|
+
egress_profiles: network.egressProfiles,
|
|
617
|
+
network_class: network.networkClass,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
353
620
|
function record(value) {
|
|
354
621
|
return value && typeof value === 'object' ? value : {};
|
|
355
622
|
}
|