@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/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
- loadItems;
8
- consumed = false;
12
+ opts;
9
13
  hasNext = true;
10
14
  nextToken;
11
- constructor(loadItems) {
12
- this.loadItems = loadItems;
15
+ constructor(opts = {}) {
16
+ this.opts = opts;
17
+ this.nextToken = opts.nextToken;
13
18
  }
14
- async nextItems() {
15
- if (this.consumed)
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.consumed = true;
18
- this.hasNext = false;
19
- return this.loadItems();
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
- pty = { create: () => unsupported('sandbox.pty') };
30
- git = { clone: () => unsupported('sandbox.git') };
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
- return new Sandbox({
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}/connect`, {
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}/connect`, {
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}/checkpoints`, {
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 checkpoints for one sandbox using snapshot naming. */
143
- static listSnapshots(sandboxId, opts = {}) {
144
- return new SnapshotPaginator(async () => {
145
- const control = new ControlClient(new ConnectionConfig(opts));
146
- const payload = await control.get(`/sandboxes/${sandboxId}/checkpoints`, {
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
- const snapshots = Array.isArray(payload.sandbox_checkpoints) ? payload.sandbox_checkpoints : [];
150
- return snapshots.map((item) => snapshotInfo(record(item)));
151
- });
152
- }
153
- /** Snapshot deletion is not backed by a Watasu checkpoint delete API yet. */
154
- static deleteSnapshot(..._args) {
155
- unsupported('Sandbox.deleteSnapshot');
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(this.sandboxId, { ...this.configOptions(), ...opts });
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
- /** List sandboxes visible to the configured API key. */
237
- static async list(opts = {}) {
238
- const control = new ControlClient(new ConnectionConfig(opts));
239
- const payload = await control.get(opts.team ? `/sandboxes?team=${encodeURIComponent(opts.team)}` : '/sandboxes');
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
- updateNetwork(..._args) { unsupported('Sandbox.updateNetwork'); }
253
- pause() { unsupported('Sandbox.pause'); }
254
- betaPause() { unsupported('Sandbox.betaPause'); }
255
- resume() { unsupported('Sandbox.resume'); }
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
  }