ppio-sandbox 2.0.0-a2 → 2.0.2-a1

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/desktop.js CHANGED
@@ -100,13 +100,16 @@ __export(desktop_exports, {
100
100
  Sandbox: () => Sandbox2,
101
101
  SandboxError: () => SandboxError,
102
102
  SandboxNotFoundError: () => SandboxNotFoundError,
103
+ Secret: () => Secret,
104
+ SecretError: () => SecretError,
103
105
  Template: () => Template,
106
+ TemplateApi: () => TemplateApi,
104
107
  TemplateBase: () => TemplateBase,
105
108
  TemplateError: () => TemplateError,
109
+ TemplatePaginator: () => TemplatePaginator,
106
110
  TimeoutError: () => TimeoutError,
107
111
  Volume: () => Volume,
108
112
  VolumeError: () => VolumeError,
109
- VolumeFileType: () => VolumeFileType,
110
113
  defaultBuildLogger: () => defaultBuildLogger,
111
114
  getSignature: () => getSignature,
112
115
  waitForFile: () => waitForFile,
@@ -124,7 +127,7 @@ var import_openapi_fetch = __toESM(require("openapi-fetch"));
124
127
  var import_platform2 = __toESM(require("platform"));
125
128
 
126
129
  // package.json
127
- var version = "2.0.0-a2";
130
+ var version = "2.0.2-a1";
128
131
 
129
132
  // src/core/utils.ts
130
133
  var import_platform = __toESM(require("platform"));
@@ -332,6 +335,12 @@ var VolumeError = class extends Error {
332
335
  this.name = "VolumeError";
333
336
  }
334
337
  };
338
+ var SecretError = class extends SandboxError {
339
+ constructor(message, stackTrace) {
340
+ super(message, stackTrace);
341
+ this.name = "SecretError";
342
+ }
343
+ };
335
344
 
336
345
  // src/core/logs.ts
337
346
  function formatLog(log) {
@@ -375,10 +384,10 @@ function createRpcLogger(logger) {
375
384
  }
376
385
  function createApiLogger(logger) {
377
386
  return {
378
- async onRequest({ request }) {
387
+ async onRequest({ request: request2 }) {
379
388
  var _a3;
380
- (_a3 = logger.info) == null ? void 0 : _a3.call(logger, `Request ${request.method} ${request.url}`);
381
- return request;
389
+ (_a3 = logger.info) == null ? void 0 : _a3.call(logger, `Request ${request2.method} ${request2.url}`);
390
+ return request2;
382
391
  },
383
392
  async onResponse({ response }) {
384
393
  var _a3, _b;
@@ -454,12 +463,27 @@ var REQUEST_TIMEOUT_MS = 6e4;
454
463
  var DEFAULT_SANDBOX_TIMEOUT_MS = 3e5;
455
464
  var KEEPALIVE_PING_INTERVAL_SEC = 50;
456
465
  var KEEPALIVE_PING_HEADER = "Keepalive-Ping-Interval";
466
+ var LEGACY_DOMAINS = ["sandbox.ppio.cn", "cn-beijing-1.sandbox.ppio.com"];
467
+ function isLegacyDomain(domain) {
468
+ return domain ? LEGACY_DOMAINS.includes(domain) : true;
469
+ }
470
+ function raiseIfLegacy(config, feature, ErrorType = Error) {
471
+ if (config.isLegacyDomain) {
472
+ throw new ErrorType(`${feature} is not supported on legacy domains`);
473
+ }
474
+ }
475
+ function validateDomain(domain) {
476
+ if (!domain) {
477
+ throw new Error("Domain cannot be empty");
478
+ }
479
+ }
457
480
  var _ConnectionConfig = class _ConnectionConfig {
458
481
  constructor(opts) {
459
482
  var _a3;
460
483
  this.apiKey = (opts == null ? void 0 : opts.apiKey) || _ConnectionConfig.apiKey;
461
484
  this.debug = (opts == null ? void 0 : opts.debug) || _ConnectionConfig.debug;
462
485
  this.domain = (opts == null ? void 0 : opts.domain) || _ConnectionConfig.domain;
486
+ this.isLegacyDomain = isLegacyDomain(this.domain);
463
487
  this.accessToken = (opts == null ? void 0 : opts.accessToken) || _ConnectionConfig.accessToken;
464
488
  this.requestTimeoutMs = (_a3 = opts == null ? void 0 : opts.requestTimeoutMs) != null ? _a3 : REQUEST_TIMEOUT_MS;
465
489
  this.logger = opts == null ? void 0 : opts.logger;
@@ -860,6 +884,9 @@ var Filesystem2 = class {
860
884
  return new Uint8Array(res.data);
861
885
  }
862
886
  if (res.response.headers.get("content-length") === "0") {
887
+ if (format === "blob") {
888
+ return new Blob([]);
889
+ }
863
890
  return "";
864
891
  }
865
892
  return res.data;
@@ -2372,64 +2399,7 @@ var Git = class {
2372
2399
  }
2373
2400
  };
2374
2401
 
2375
- // src/core/volume/client.ts
2376
- var import_openapi_fetch3 = __toESM(require("openapi-fetch"));
2377
- var FILE_TIMEOUT_MS = 36e5;
2378
- var VolumeConnectionConfig = class _VolumeConnectionConfig {
2379
- constructor(volume, opts) {
2380
- var _a3, _b;
2381
- this.domain = (opts == null ? void 0 : opts.domain) || volume.domain || _VolumeConnectionConfig.domain;
2382
- this.debug = (_b = (_a3 = opts == null ? void 0 : opts.debug) != null ? _a3 : volume.debug) != null ? _b : _VolumeConnectionConfig.debug;
2383
- this.apiUrl = (opts == null ? void 0 : opts.apiUrl) || _VolumeConnectionConfig.volumeApiUrl || (this.debug ? "http://localhost:8080" : `https://api.${this.domain}`);
2384
- this.token = (opts == null ? void 0 : opts.token) || volume.token;
2385
- this.headers = opts == null ? void 0 : opts.headers;
2386
- this.logger = opts == null ? void 0 : opts.logger;
2387
- this.requestTimeoutMs = opts == null ? void 0 : opts.requestTimeoutMs;
2388
- }
2389
- static get domain() {
2390
- return getEnvVar("PPIO_DOMAIN") || "sandbox.ppio.cn";
2391
- }
2392
- static get debug() {
2393
- return (getEnvVar("PPIO_DEBUG") || "false").toLowerCase() === "true";
2394
- }
2395
- static get volumeApiUrl() {
2396
- return getEnvVar("PPIO_SANDBOX_VOLUME_API_URL");
2397
- }
2398
- getSignal(requestTimeoutMs) {
2399
- const timeout = requestTimeoutMs != null ? requestTimeoutMs : this.requestTimeoutMs;
2400
- return timeout ? AbortSignal.timeout(timeout) : void 0;
2401
- }
2402
- };
2403
- var VolumeApiClient = class {
2404
- constructor(config) {
2405
- this.api = (0, import_openapi_fetch3.default)({
2406
- baseUrl: config.apiUrl,
2407
- headers: __spreadValues(__spreadValues(__spreadValues({}, defaultHeaders), config.token && { Authorization: `Bearer ${config.token}` }), config.headers)
2408
- });
2409
- if (config.logger) {
2410
- this.api.use(createApiLogger(config.logger));
2411
- }
2412
- }
2413
- };
2414
-
2415
- // src/core/volume/types.ts
2416
- var VolumeFileType = /* @__PURE__ */ ((VolumeFileType2) => {
2417
- VolumeFileType2["UNKNOWN"] = "unknown";
2418
- VolumeFileType2["FILE"] = "file";
2419
- VolumeFileType2["DIRECTORY"] = "directory";
2420
- VolumeFileType2["SYMLINK"] = "symlink";
2421
- return VolumeFileType2;
2422
- })(VolumeFileType || {});
2423
-
2424
2402
  // src/core/volume/index.ts
2425
- function convertVolumeEntryStat(entry) {
2426
- return __spreadProps(__spreadValues({}, entry), {
2427
- type: entry.type,
2428
- atime: new Date(entry.atime),
2429
- mtime: new Date(entry.mtime),
2430
- ctime: new Date(entry.ctime)
2431
- });
2432
- }
2433
2403
  var Volume = class _Volume {
2434
2404
  /**
2435
2405
  * Create a local Volume instance with no API call.
@@ -2441,6 +2411,9 @@ var Volume = class _Volume {
2441
2411
  * @param debug whether to use debug mode.
2442
2412
  */
2443
2413
  constructor(volumeId, name, token, domain, debug) {
2414
+ if (domain) {
2415
+ validateDomain(domain);
2416
+ }
2444
2417
  this.volumeId = volumeId;
2445
2418
  this.name = name;
2446
2419
  this.token = token;
@@ -2452,15 +2425,20 @@ var Volume = class _Volume {
2452
2425
  *
2453
2426
  * @param name name of the volume.
2454
2427
  * @param opts connection options.
2428
+ * @param [opts.quotaSizeGiB] capacity quota in GiB.
2429
+ * @param [opts.quotaInodes] inode quota.
2455
2430
  *
2456
2431
  * @returns new Volume instance.
2457
2432
  */
2458
2433
  static async create(name, opts) {
2459
2434
  const config = new ConnectionConfig(opts);
2435
+ raiseIfLegacy(config, "Volume.create", VolumeError);
2460
2436
  const client = new ApiClient(config);
2461
2437
  const res = await client.api.POST("/volumes", {
2462
2438
  body: {
2463
- name
2439
+ name,
2440
+ quotaSizeGiB: opts == null ? void 0 : opts.quotaSizeGiB,
2441
+ quotaInodes: opts == null ? void 0 : opts.quotaInodes
2464
2442
  },
2465
2443
  signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
2466
2444
  });
@@ -2489,6 +2467,7 @@ var Volume = class _Volume {
2489
2467
  */
2490
2468
  static async connect(volumeId, opts) {
2491
2469
  const config = new ConnectionConfig(opts);
2470
+ raiseIfLegacy(config, "Volume.connect", VolumeError);
2492
2471
  const { name, token } = await _Volume.getInfo(volumeId, opts);
2493
2472
  return new _Volume(volumeId, name, token, config.domain, config.debug);
2494
2473
  }
@@ -2501,7 +2480,9 @@ var Volume = class _Volume {
2501
2480
  * @returns volume information.
2502
2481
  */
2503
2482
  static async getInfo(volumeId, opts) {
2483
+ var _a3, _b, _c, _d;
2504
2484
  const config = new ConnectionConfig(opts);
2485
+ raiseIfLegacy(config, "Volume.getInfo", VolumeError);
2505
2486
  const client = new ApiClient(config);
2506
2487
  const res = await client.api.GET("/volumes/{volumeID}", {
2507
2488
  params: {
@@ -2518,10 +2499,15 @@ var Volume = class _Volume {
2518
2499
  if (err) {
2519
2500
  throw err;
2520
2501
  }
2502
+ const data = res.data;
2521
2503
  return {
2522
- volumeId: res.data.volumeID,
2523
- name: res.data.name,
2524
- token: res.data.token
2504
+ volumeId: data.volumeID,
2505
+ name: data.name,
2506
+ token: data.token,
2507
+ quotaSizeGiB: (_a3 = data.quotaSizeGiB) != null ? _a3 : 0,
2508
+ quotaInodes: (_b = data.quotaInodes) != null ? _b : 0,
2509
+ usedSizeBytes: (_c = data.usedSizeBytes) != null ? _c : 0,
2510
+ usedInodes: (_d = data.usedInodes) != null ? _d : 0
2525
2511
  };
2526
2512
  }
2527
2513
  /**
@@ -2534,6 +2520,7 @@ var Volume = class _Volume {
2534
2520
  static async list(opts) {
2535
2521
  var _a3;
2536
2522
  const config = new ConnectionConfig(opts);
2523
+ raiseIfLegacy(config, "Volume.list", VolumeError);
2537
2524
  const client = new ApiClient(config);
2538
2525
  const res = await client.api.GET("/volumes", {
2539
2526
  signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
@@ -2542,10 +2529,61 @@ var Volume = class _Volume {
2542
2529
  if (err) {
2543
2530
  throw err;
2544
2531
  }
2545
- return ((_a3 = res.data) != null ? _a3 : []).map((vol) => ({
2546
- volumeId: vol.volumeID,
2547
- name: vol.name
2548
- }));
2532
+ return ((_a3 = res.data) != null ? _a3 : []).map((vol) => {
2533
+ var _a4, _b, _c, _d;
2534
+ return {
2535
+ volumeId: vol.volumeID,
2536
+ name: vol.name,
2537
+ quotaSizeGiB: (_a4 = vol.quotaSizeGiB) != null ? _a4 : 0,
2538
+ quotaInodes: (_b = vol.quotaInodes) != null ? _b : 0,
2539
+ usedSizeBytes: (_c = vol.usedSizeBytes) != null ? _c : 0,
2540
+ usedInodes: (_d = vol.usedInodes) != null ? _d : 0
2541
+ };
2542
+ });
2543
+ }
2544
+ /**
2545
+ * Update volume quota.
2546
+ *
2547
+ * @param volumeId volume ID.
2548
+ * @param params quota parameters.
2549
+ * @param opts connection options.
2550
+ *
2551
+ * @returns updated volume information.
2552
+ */
2553
+ static async updateQuota(volumeId, params, opts) {
2554
+ var _a3, _b, _c, _d;
2555
+ const config = new ConnectionConfig(opts);
2556
+ raiseIfLegacy(config, "Volume.updateQuota", VolumeError);
2557
+ const res = await fetch(
2558
+ `${config.apiUrl}/volumes/${encodeURIComponent(volumeId)}`,
2559
+ {
2560
+ method: "PATCH",
2561
+ headers: __spreadValues(__spreadValues(__spreadProps(__spreadValues({}, config.headers), {
2562
+ "Content-Type": "application/json"
2563
+ }), config.apiKey ? { "X-API-KEY": config.apiKey } : {}), config.accessToken ? { Authorization: `Bearer ${config.accessToken}` } : {}),
2564
+ body: JSON.stringify({
2565
+ quotaSizeGiB: params.quotaSizeGiB,
2566
+ quotaInodes: params.quotaInodes
2567
+ }),
2568
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
2569
+ }
2570
+ );
2571
+ if (res.status === 404) {
2572
+ throw new NotFoundError(`Volume ${volumeId} not found`);
2573
+ }
2574
+ if (!res.ok) {
2575
+ throw new VolumeError(`${res.status}: ${res.statusText}`);
2576
+ }
2577
+ const data = await res.json();
2578
+ return {
2579
+ volumeId: data.volumeID,
2580
+ name: data.name,
2581
+ token: data.token,
2582
+ quotaSizeGiB: (_a3 = data.quotaSizeGiB) != null ? _a3 : 0,
2583
+ quotaInodes: (_b = data.quotaInodes) != null ? _b : 0,
2584
+ usedSizeBytes: (_c = data.usedSizeBytes) != null ? _c : 0,
2585
+ usedInodes: (_d = data.usedInodes) != null ? _d : 0
2586
+ };
2549
2587
  }
2550
2588
  /**
2551
2589
  * Destroy a volume.
@@ -2555,6 +2593,7 @@ var Volume = class _Volume {
2555
2593
  */
2556
2594
  static async destroy(volumeId, opts) {
2557
2595
  const config = new ConnectionConfig(opts);
2596
+ raiseIfLegacy(config, "Volume.destroy", VolumeError);
2558
2597
  const client = new ApiClient(config);
2559
2598
  const res = await client.api.DELETE("/volumes/{volumeID}", {
2560
2599
  params: {
@@ -2573,296 +2612,196 @@ var Volume = class _Volume {
2573
2612
  }
2574
2613
  return true;
2575
2614
  }
2576
- /**
2577
- * List directory contents.
2578
- *
2579
- * @param path path to the directory.
2580
- * @param opts connection options.
2581
- * @param [opts.depth] number of layers deep to recurse into the directory (default: 1).
2582
- *
2583
- * @returns list of entries in the directory.
2584
- */
2585
- async list(path2, opts) {
2586
- const config = new VolumeConnectionConfig(this, opts);
2587
- const client = new VolumeApiClient(config);
2588
- const res = await client.api.GET("/volumecontent/{volumeID}/dir", {
2589
- params: {
2590
- path: {
2591
- volumeID: this.volumeId
2592
- },
2593
- query: {
2594
- path: path2,
2595
- depth: opts == null ? void 0 : opts.depth
2596
- }
2597
- },
2598
- signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
2599
- });
2600
- if (res.response.status === 404) {
2601
- throw new NotFoundError(`Path ${path2} not found`);
2602
- }
2603
- const err = handleApiError(res, VolumeError);
2604
- if (err) {
2605
- throw err;
2606
- }
2607
- const entries = Array.isArray(res.data) ? res.data : [];
2608
- return entries.map(convertVolumeEntryStat);
2615
+ };
2616
+
2617
+ // src/core/secret/index.ts
2618
+ var SECRET_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
2619
+ var ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
2620
+ var HOST_PATTERN = /^(\*\.)?[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
2621
+ function validateSecretEnvs(secretEnvs, envs) {
2622
+ if (!secretEnvs) {
2623
+ return {};
2609
2624
  }
2610
- /**
2611
- * Create a directory.
2612
- *
2613
- * @param path path to the directory to create.
2614
- * @param options directory creation options.
2615
- * @param opts connection options.
2616
- */
2617
- async makeDir(path2, opts) {
2618
- const config = new VolumeConnectionConfig(this, opts);
2619
- const client = new VolumeApiClient(config);
2620
- const res = await client.api.POST("/volumecontent/{volumeID}/dir", {
2621
- params: {
2622
- path: {
2623
- volumeID: this.volumeId
2624
- },
2625
- query: {
2626
- path: path2,
2627
- uid: opts == null ? void 0 : opts.uid,
2628
- gid: opts == null ? void 0 : opts.gid,
2629
- mode: opts == null ? void 0 : opts.mode,
2630
- force: opts == null ? void 0 : opts.force
2631
- }
2632
- },
2633
- signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
2634
- });
2635
- if (res.response.status === 404) {
2636
- throw new NotFoundError(`Path ${path2} not found`);
2637
- }
2638
- const err = handleApiError(res, VolumeError);
2639
- if (err) {
2640
- throw err;
2625
+ if (typeof secretEnvs !== "object" || Array.isArray(secretEnvs)) {
2626
+ throw new InvalidArgumentError("secretEnvs must be a mapping");
2627
+ }
2628
+ const envNames = new Set(Object.keys(envs != null ? envs : {}));
2629
+ const validated = {};
2630
+ for (const [envName, secretName] of Object.entries(secretEnvs)) {
2631
+ if (!ENV_NAME_PATTERN.test(envName)) {
2632
+ throw new InvalidArgumentError(
2633
+ `secretEnvs key ${JSON.stringify(envName)} must be a valid sandbox environment variable name`
2634
+ );
2641
2635
  }
2642
- if (!res.data) {
2643
- throw new Error("Response data is missing");
2636
+ if (envNames.has(envName)) {
2637
+ throw new InvalidArgumentError(
2638
+ `envs and secretEnvs both define ${JSON.stringify(envName)}`
2639
+ );
2644
2640
  }
2645
- return convertVolumeEntryStat(
2646
- res.data
2641
+ validateSecretName(secretName);
2642
+ validated[envName] = secretName;
2643
+ }
2644
+ return validated;
2645
+ }
2646
+ var _Secret = class _Secret {
2647
+ static async create(params, opts) {
2648
+ validateSecretName(params.name);
2649
+ if (!params.value) {
2650
+ throw new InvalidArgumentError("value is required");
2651
+ }
2652
+ const body = {
2653
+ name: params.name,
2654
+ value: params.value,
2655
+ hosts: normalizeSecretHosts(params.hosts)
2656
+ };
2657
+ if (params.description !== void 0) {
2658
+ body.description = params.description;
2659
+ }
2660
+ return bindingFromJson(
2661
+ await request(
2662
+ "POST",
2663
+ _Secret.base,
2664
+ "Secret.create",
2665
+ opts,
2666
+ body
2667
+ )
2647
2668
  );
2648
2669
  }
2649
- /**
2650
- * Get information about a file or directory.
2651
- *
2652
- * @param path path to the file or directory.
2653
- * @param opts connection options.
2654
- *
2655
- * @returns information about the entry.
2656
- */
2657
- async getInfo(path2, opts) {
2658
- const config = new VolumeConnectionConfig(this, opts);
2659
- const client = new VolumeApiClient(config);
2660
- const res = await client.api.GET("/volumecontent/{volumeID}/path", {
2661
- params: {
2662
- path: {
2663
- volumeID: this.volumeId
2664
- },
2665
- query: {
2666
- path: path2
2667
- }
2668
- },
2669
- signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
2670
- });
2671
- if (res.response.status === 404) {
2672
- throw new NotFoundError(`Path ${path2} not found`);
2673
- }
2674
- const err = handleApiError(res, VolumeError);
2675
- if (err) {
2676
- throw err;
2677
- }
2678
- if (!res.data) {
2679
- throw new Error("Response data is missing");
2680
- }
2681
- return convertVolumeEntryStat(
2682
- res.data
2670
+ static async list(opts) {
2671
+ var _a3;
2672
+ const data = await request(
2673
+ "GET",
2674
+ _Secret.base,
2675
+ "Secret.list",
2676
+ opts
2677
+ );
2678
+ return ((_a3 = data.secrets) != null ? _a3 : []).map(bindingFromJson);
2679
+ }
2680
+ static async get(name, opts) {
2681
+ validateSecretName(name);
2682
+ return bindingFromJson(
2683
+ await request(
2684
+ "GET",
2685
+ `${_Secret.base}/${encodeURIComponent(name)}`,
2686
+ "Secret.get",
2687
+ opts
2688
+ )
2683
2689
  );
2684
2690
  }
2685
- /**
2686
- * Check whether a file or directory exists.
2687
- *
2688
- * Uses {@link getInfo} under the hood. Returns `true` if the path exists,
2689
- * `false` if it does not (404). Other errors are rethrown.
2690
- *
2691
- * @param path path to the file or directory.
2692
- * @param opts connection options.
2693
- *
2694
- * @returns `true` if the path exists, `false` otherwise.
2695
- */
2696
- async exists(path2, opts) {
2697
- try {
2698
- await this.getInfo(path2, opts);
2699
- return true;
2700
- } catch (err) {
2701
- if (err instanceof NotFoundError) {
2702
- return false;
2703
- }
2704
- throw err;
2691
+ static async update(params, opts) {
2692
+ validateSecretName(params.name);
2693
+ if (!params.value) {
2694
+ throw new InvalidArgumentError("value is required");
2705
2695
  }
2696
+ const body = {
2697
+ value: params.value,
2698
+ hosts: normalizeSecretHosts(params.hosts)
2699
+ };
2700
+ if (params.description !== void 0) {
2701
+ body.description = params.description;
2702
+ }
2703
+ return bindingFromJson(
2704
+ await request(
2705
+ "PUT",
2706
+ `${_Secret.base}/${encodeURIComponent(params.name)}`,
2707
+ "Secret.update",
2708
+ opts,
2709
+ body
2710
+ )
2711
+ );
2706
2712
  }
2707
- /**
2708
- * Update file or directory metadata.
2709
- *
2710
- * @param path path to the file or directory.
2711
- * @param metadata metadata to update (uid, gid, mode).
2712
- * @param opts connection options.
2713
- *
2714
- * @returns updated entry information.
2715
- */
2716
- async updateMetadata(path2, metadata, opts) {
2717
- const config = new VolumeConnectionConfig(this, opts);
2718
- const client = new VolumeApiClient(config);
2719
- const res = await client.api.PATCH("/volumecontent/{volumeID}/path", {
2720
- params: {
2721
- path: {
2722
- volumeID: this.volumeId
2723
- },
2724
- query: {
2725
- path: path2
2726
- }
2727
- },
2728
- body: {
2729
- uid: metadata.uid,
2730
- gid: metadata.gid,
2731
- mode: metadata.mode
2732
- },
2733
- signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
2734
- });
2735
- if (res.response.status === 404) {
2736
- throw new NotFoundError(`Path ${path2} not found`);
2737
- }
2738
- const err = handleApiError(res, VolumeError);
2739
- if (err) {
2740
- throw err;
2741
- }
2742
- if (!res.data) {
2743
- throw new Error("Response data is missing");
2744
- }
2745
- return convertVolumeEntryStat(
2746
- res.data
2713
+ static async delete(name, opts) {
2714
+ var _a3;
2715
+ validateSecretName(name);
2716
+ const data = await request(
2717
+ "DELETE",
2718
+ `${_Secret.base}/${encodeURIComponent(name)}`,
2719
+ "Secret.delete",
2720
+ opts
2747
2721
  );
2722
+ return String((_a3 = data.deleted) != null ? _a3 : "");
2748
2723
  }
2749
- async readFile(path2, opts) {
2750
- var _a3, _b;
2751
- const format = (_a3 = opts == null ? void 0 : opts.format) != null ? _a3 : "text";
2752
- const config = new VolumeConnectionConfig(this, opts);
2753
- const client = new VolumeApiClient(config);
2754
- const res = await client.api.GET("/volumecontent/{volumeID}/file", {
2755
- params: {
2756
- path: {
2757
- volumeID: this.volumeId
2758
- },
2759
- query: {
2760
- path: path2
2761
- }
2762
- },
2763
- parseAs: format === "bytes" ? "arrayBuffer" : format,
2764
- signal: config.getSignal((_b = opts == null ? void 0 : opts.requestTimeoutMs) != null ? _b : FILE_TIMEOUT_MS)
2765
- });
2766
- if (res.response.status === 404) {
2767
- throw new NotFoundError(`Path ${path2} not found`);
2768
- }
2769
- const err = handleApiError(res, VolumeError);
2770
- if (err) {
2771
- throw err;
2724
+ };
2725
+ _Secret.base = "/secrets";
2726
+ var Secret = _Secret;
2727
+ function validateSecretName(name) {
2728
+ if (typeof name !== "string" || !SECRET_NAME_PATTERN.test(name)) {
2729
+ throw new InvalidArgumentError(
2730
+ `secret name ${JSON.stringify(name)} must be 1-128 characters and contain only letters, numbers, dots, underscores, and hyphens`
2731
+ );
2732
+ }
2733
+ }
2734
+ function normalizeSecretHosts(hosts) {
2735
+ if (!hosts.length) {
2736
+ throw new InvalidArgumentError("hosts is required");
2737
+ }
2738
+ const seen = /* @__PURE__ */ new Set();
2739
+ const normalizedHosts = [];
2740
+ for (const rawHost of hosts) {
2741
+ if (typeof rawHost !== "string") {
2742
+ throw new InvalidArgumentError("host must be a string");
2772
2743
  }
2773
- if (format === "bytes") {
2774
- return new Uint8Array(res.data);
2744
+ const host = rawHost.trim().toLowerCase();
2745
+ if (!host) {
2746
+ throw new InvalidArgumentError("host cannot be empty");
2775
2747
  }
2776
- if (format === "text") {
2777
- if (res.response.headers.get("content-length") === "0") {
2778
- return "";
2779
- }
2780
- return typeof res.data === "string" ? res.data : "";
2748
+ if (host.includes("://") || /[/?#@:]/.test(host)) {
2749
+ throw new InvalidArgumentError(
2750
+ `host ${JSON.stringify(host)} must be a hostname only`
2751
+ );
2781
2752
  }
2782
- return res.data;
2783
- }
2784
- /**
2785
- * Write content to a file.
2786
- *
2787
- * Writing to a file that doesn't exist creates the file.
2788
- *
2789
- * Writing to a file that already exists overwrites the file.
2790
- *
2791
- * @param path path to the file.
2792
- * @param data data to write to the file. Data can be a string, `ArrayBuffer`, `Blob`, or `ReadableStream`.
2793
- * @param options file creation options.
2794
- * @param opts connection options.
2795
- *
2796
- * @returns information about the written file
2797
- */
2798
- async writeFile(path2, data, opts) {
2799
- var _a3;
2800
- const config = new VolumeConnectionConfig(this, opts);
2801
- const client = new VolumeApiClient(config);
2802
- const blob = await toBlob(data);
2803
- const res = await client.api.PUT("/volumecontent/{volumeID}/file", {
2804
- params: {
2805
- path: {
2806
- volumeID: this.volumeId
2807
- },
2808
- query: {
2809
- path: path2,
2810
- uid: opts == null ? void 0 : opts.uid,
2811
- gid: opts == null ? void 0 : opts.gid,
2812
- mode: opts == null ? void 0 : opts.mode,
2813
- force: opts == null ? void 0 : opts.force
2814
- }
2815
- },
2816
- bodySerializer: () => blob,
2817
- body: {},
2818
- headers: {
2819
- "Content-Type": "application/octet-stream"
2820
- },
2821
- signal: config.getSignal((_a3 = opts == null ? void 0 : opts.requestTimeoutMs) != null ? _a3 : FILE_TIMEOUT_MS)
2822
- });
2823
- if (res.response.status === 404) {
2824
- throw new NotFoundError(`Path ${path2} not found`);
2753
+ const wildcard = host.startsWith("*.");
2754
+ const lookupHost = wildcard ? host.slice(2) : host;
2755
+ if (!lookupHost || lookupHost.includes("*")) {
2756
+ throw new InvalidArgumentError(`invalid host ${JSON.stringify(host)}`);
2825
2757
  }
2826
- const err = handleApiError(res, VolumeError);
2827
- if (err) {
2828
- throw err;
2758
+ const normalized = wildcard ? `*.${lookupHost}` : lookupHost;
2759
+ if (!HOST_PATTERN.test(normalized)) {
2760
+ throw new InvalidArgumentError(
2761
+ `host ${JSON.stringify(host)} must be a hostname or wildcard hostname`
2762
+ );
2829
2763
  }
2830
- if (!res.data) {
2831
- throw new Error("Response data is missing");
2764
+ if (seen.has(normalized)) {
2765
+ continue;
2832
2766
  }
2833
- return convertVolumeEntryStat(
2834
- res.data
2835
- );
2767
+ seen.add(normalized);
2768
+ normalizedHosts.push(normalized);
2836
2769
  }
2837
- /**
2838
- * Remove a file or directory.
2839
- *
2840
- * @param path path to the file or directory to remove.
2841
- * @param opts connection options.
2842
- */
2843
- async remove(path2, opts) {
2844
- const config = new VolumeConnectionConfig(this, opts);
2845
- const client = new VolumeApiClient(config);
2846
- const res = await client.api.DELETE("/volumecontent/{volumeID}/path", {
2847
- params: {
2848
- path: {
2849
- volumeID: this.volumeId
2850
- },
2851
- query: {
2852
- path: path2
2853
- }
2854
- },
2855
- signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
2856
- });
2857
- if (res.response.status === 404) {
2858
- throw new NotFoundError(`Path ${path2} not found`);
2859
- }
2860
- const err = handleApiError(res, VolumeError);
2861
- if (err) {
2862
- throw err;
2863
- }
2770
+ return normalizedHosts;
2771
+ }
2772
+ async function request(method, path2, feature, opts, body) {
2773
+ const config = new ConnectionConfig(opts);
2774
+ raiseIfLegacy(config, feature, SecretError);
2775
+ const headers = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, config.headers), body ? { "Content-Type": "application/json" } : {}), config.apiKey ? { "X-API-KEY": config.apiKey } : {}), config.accessToken ? { Authorization: `Bearer ${config.accessToken}` } : {});
2776
+ const response = await fetch(`${config.apiUrl}${path2}`, {
2777
+ method,
2778
+ headers,
2779
+ body: body ? JSON.stringify(body) : void 0,
2780
+ signal: config.getSignal()
2781
+ });
2782
+ const data = await parseJson(response);
2783
+ if (!response.ok) {
2784
+ const message = typeof (data == null ? void 0 : data.message) === "string" ? data.message : response.statusText;
2785
+ throw new SecretError(`${response.status}: ${message}`);
2864
2786
  }
2865
- };
2787
+ return data;
2788
+ }
2789
+ async function parseJson(response) {
2790
+ const text = await response.text();
2791
+ return text ? JSON.parse(text) : {};
2792
+ }
2793
+ function bindingFromJson(data) {
2794
+ var _a3, _b;
2795
+ return {
2796
+ name: data.name,
2797
+ hosts: (_a3 = data.hosts) != null ? _a3 : [],
2798
+ placeholder: data.placeholder,
2799
+ description: (_b = data.description) != null ? _b : null,
2800
+ hasSecret: data.hasSecret,
2801
+ status: data.status,
2802
+ updatedAt: data.updatedAt
2803
+ };
2804
+ }
2866
2805
 
2867
2806
  // src/core/sandbox/index.ts
2868
2807
  var import_connect_web = require("@connectrpc/connect-web");
@@ -3333,6 +3272,33 @@ var Commands = class {
3333
3272
 
3334
3273
  // src/core/sandbox/sandboxApi.ts
3335
3274
  var import_compare_versions4 = require("compare-versions");
3275
+ async function createRawApiError(response) {
3276
+ var _a3;
3277
+ let message = response.statusText;
3278
+ try {
3279
+ const body = await response.json();
3280
+ message = (_a3 = body == null ? void 0 : body.message) != null ? _a3 : JSON.stringify(body);
3281
+ } catch (e) {
3282
+ try {
3283
+ message = await response.text();
3284
+ } catch (e2) {
3285
+ }
3286
+ }
3287
+ if (response.status === 401) {
3288
+ return new AuthenticationError(
3289
+ `Unauthorized, please check your credentials. - ${message}`
3290
+ );
3291
+ }
3292
+ if (response.status === 429) {
3293
+ return new RateLimitError(
3294
+ `Rate limit exceeded, please try again later - ${message}`
3295
+ );
3296
+ }
3297
+ return new SandboxError(`${response.status}: ${message}`);
3298
+ }
3299
+ function getRawApiHeaders(config, headers) {
3300
+ return __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, defaultHeaders), config.apiKey ? { "X-API-KEY": config.apiKey } : {}), config.accessToken ? { Authorization: `Bearer ${config.accessToken}` } : {}), config.headers), headers != null ? headers : {});
3301
+ }
3336
3302
  function getLifecycle(opts) {
3337
3303
  if (opts == null ? void 0 : opts.lifecycle) {
3338
3304
  return opts.lifecycle;
@@ -3403,15 +3369,40 @@ var SandboxApi = class {
3403
3369
  * @returns List of sandbox metrics containing CPU, memory and disk usage information.
3404
3370
  */
3405
3371
  static async getMetrics(sandboxId, opts) {
3406
- var _a3, _b;
3372
+ var _a3, _b, _c, _d;
3407
3373
  const config = new ConnectionConfig(opts);
3408
3374
  const client = new ApiClient(config);
3375
+ if (config.isLegacyDomain) {
3376
+ const res2 = await client.api.GET("/sandboxes/{sandboxID}/metrics", {
3377
+ params: {
3378
+ path: {
3379
+ sandboxID: sandboxId
3380
+ }
3381
+ },
3382
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
3383
+ });
3384
+ const err2 = handleApiError(res2);
3385
+ if (err2) {
3386
+ throw err2;
3387
+ }
3388
+ return (_b = ((_a3 = res2.data) != null ? _a3 : []).map((metric) => ({
3389
+ timestamp: new Date(metric.timestamp),
3390
+ cpuUsedPct: metric.cpuUsedPct,
3391
+ cpuCount: metric.cpuCount,
3392
+ memUsed: metric.memUsedMiB * 1024 * 1024,
3393
+ memTotal: metric.memTotalMiB * 1024 * 1024,
3394
+ diskUsed: -1,
3395
+ diskTotal: -1
3396
+ }))) != null ? _b : [];
3397
+ }
3409
3398
  const start = (opts == null ? void 0 : opts.start) ? Math.round(opts.start.getTime() / 1e3) : void 0;
3410
3399
  const end = (opts == null ? void 0 : opts.end) ? Math.round(opts.end.getTime() / 1e3) : void 0;
3411
3400
  const res = await client.api.GET("/sandboxes/{sandboxID}/metrics", {
3412
3401
  params: {
3413
3402
  path: {
3414
- sandboxID: sandboxId,
3403
+ sandboxID: sandboxId
3404
+ },
3405
+ query: {
3415
3406
  start,
3416
3407
  end
3417
3408
  }
@@ -3422,7 +3413,7 @@ var SandboxApi = class {
3422
3413
  if (err) {
3423
3414
  throw err;
3424
3415
  }
3425
- return (_b = (_a3 = res.data) == null ? void 0 : _a3.map((metric) => ({
3416
+ return (_d = (_c = res.data) == null ? void 0 : _c.map((metric) => ({
3426
3417
  timestamp: new Date(metric.timestamp),
3427
3418
  cpuUsedPct: metric.cpuUsedPct,
3428
3419
  cpuCount: metric.cpuCount,
@@ -3430,7 +3421,7 @@ var SandboxApi = class {
3430
3421
  memTotal: metric.memTotal,
3431
3422
  diskUsed: metric.diskUsed,
3432
3423
  diskTotal: metric.diskTotal
3433
- }))) != null ? _b : [];
3424
+ }))) != null ? _d : [];
3434
3425
  }
3435
3426
  /**
3436
3427
  * Set the timeout of the specified sandbox.
@@ -3459,17 +3450,238 @@ var SandboxApi = class {
3459
3450
  },
3460
3451
  signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
3461
3452
  });
3462
- if (((_a3 = res.error) == null ? void 0 : _a3.code) === 404) {
3453
+ if (((_a3 = res.error) == null ? void 0 : _a3.code) === 404) {
3454
+ throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
3455
+ }
3456
+ const err = handleApiError(res);
3457
+ if (err) {
3458
+ throw err;
3459
+ }
3460
+ }
3461
+ /**
3462
+ * @deprecated Only legacy domains support this method. Use {@link SandboxApi.createSnapshot} for modern domains.
3463
+ */
3464
+ static async commit(sandboxId, opts) {
3465
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j;
3466
+ const config = new ConnectionConfig(opts);
3467
+ if (!config.isLegacyDomain) {
3468
+ throw new SandboxError("Sandbox.commit is only supported for legacy domains");
3469
+ }
3470
+ const response = await fetch(
3471
+ `${config.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/commit`,
3472
+ {
3473
+ method: "POST",
3474
+ headers: getRawApiHeaders(config, {
3475
+ "Content-Type": "application/json"
3476
+ }),
3477
+ body: JSON.stringify((opts == null ? void 0 : opts.alias) ? { alias: opts.alias } : {}),
3478
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
3479
+ }
3480
+ );
3481
+ if (response.status === 404) {
3482
+ throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
3483
+ }
3484
+ if (!response.ok) {
3485
+ throw await createRawApiError(response);
3486
+ }
3487
+ const data = await response.json();
3488
+ return {
3489
+ alias: (_d = (_c = (_a3 = data.aliases) == null ? void 0 : _a3[0]) != null ? _c : (_b = data.names) == null ? void 0 : _b[0]) != null ? _d : "",
3490
+ name: (_h = (_g = (_e = data.names) == null ? void 0 : _e[0]) != null ? _g : (_f = data.aliases) == null ? void 0 : _f[0]) != null ? _h : "",
3491
+ tags: [],
3492
+ templateId: data.templateID,
3493
+ buildId: data.buildID,
3494
+ aliases: (_i = data.aliases) != null ? _i : [],
3495
+ names: (_j = data.names) != null ? _j : [],
3496
+ templateType: "snapshot_template"
3497
+ };
3498
+ }
3499
+ static async hotplugMemory(sandboxId, requestedSizeMib, opts) {
3500
+ const config = new ConnectionConfig(opts);
3501
+ raiseIfLegacy(config, "Sandbox.hotplugMemory", SandboxError);
3502
+ const response = await fetch(
3503
+ `${config.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/hotplug-memory`,
3504
+ {
3505
+ method: "POST",
3506
+ headers: getRawApiHeaders(config, {
3507
+ "Content-Type": "application/json"
3508
+ }),
3509
+ body: JSON.stringify({
3510
+ requested_size_mib: requestedSizeMib
3511
+ }),
3512
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
3513
+ }
3514
+ );
3515
+ if (response.status === 404) {
3516
+ throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
3517
+ }
3518
+ if (!response.ok) {
3519
+ throw await createRawApiError(response);
3520
+ }
3521
+ }
3522
+ static async reset(sandboxId, opts) {
3523
+ var _a3;
3524
+ const config = new ConnectionConfig(opts);
3525
+ const response = await fetch(
3526
+ `${config.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/reset`,
3527
+ {
3528
+ method: "POST",
3529
+ headers: getRawApiHeaders(config, {
3530
+ "Content-Type": "application/json"
3531
+ }),
3532
+ body: JSON.stringify(__spreadValues({
3533
+ resume: (_a3 = opts == null ? void 0 : opts.resume) != null ? _a3 : true
3534
+ }, (opts == null ? void 0 : opts.timeoutMs) !== void 0 ? { timeout: timeoutToSeconds(opts.timeoutMs) } : {})),
3535
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
3536
+ }
3537
+ );
3538
+ if (response.status === 404) {
3539
+ throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
3540
+ }
3541
+ if (response.status === 409) {
3542
+ return false;
3543
+ }
3544
+ if (!response.ok) {
3545
+ throw await createRawApiError(response);
3546
+ }
3547
+ return true;
3548
+ }
3549
+ static async mountVolume(sandboxId, name, path2, opts) {
3550
+ var _a3, _b;
3551
+ const config = new ConnectionConfig(opts);
3552
+ raiseIfLegacy(config, "Sandbox.mountVolume", SandboxError);
3553
+ const response = await fetch(
3554
+ `${config.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/volume-mounts`,
3555
+ {
3556
+ method: "POST",
3557
+ headers: getRawApiHeaders(config, {
3558
+ "Content-Type": "application/json"
3559
+ }),
3560
+ body: JSON.stringify({ name, path: path2 }),
3561
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
3562
+ }
3563
+ );
3564
+ if (response.status === 404) {
3565
+ throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
3566
+ }
3567
+ if (!response.ok) {
3568
+ throw await createRawApiError(response);
3569
+ }
3570
+ const data = await response.json();
3571
+ return {
3572
+ sandboxId: data.sandboxID,
3573
+ templateId: data.templateID,
3574
+ name: data.name,
3575
+ metadata: (_a3 = data.metadata) != null ? _a3 : {},
3576
+ startedAt: new Date(data.startedAt),
3577
+ endAt: new Date(data.endAt),
3578
+ state: data.state,
3579
+ cpuCount: data.cpuCount,
3580
+ memoryMB: data.memoryMB,
3581
+ envdVersion: data.envdVersion,
3582
+ volumeMounts: (_b = data.volumeMounts) != null ? _b : []
3583
+ };
3584
+ }
3585
+ static async unmountVolume(sandboxId, path2, opts) {
3586
+ var _a3, _b;
3587
+ const config = new ConnectionConfig(opts);
3588
+ raiseIfLegacy(config, "Sandbox.unmountVolume", SandboxError);
3589
+ const url = new URL(
3590
+ `${config.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/volume-mounts`
3591
+ );
3592
+ url.searchParams.set("path", path2);
3593
+ if (opts == null ? void 0 : opts.force) {
3594
+ url.searchParams.set("force", "true");
3595
+ }
3596
+ const response = await fetch(url, {
3597
+ method: "DELETE",
3598
+ headers: getRawApiHeaders(config),
3599
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
3600
+ });
3601
+ if (response.status === 404) {
3463
3602
  throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
3464
3603
  }
3465
- const err = handleApiError(res);
3466
- if (err) {
3467
- throw err;
3604
+ if (!response.ok) {
3605
+ throw await createRawApiError(response);
3468
3606
  }
3607
+ const data = await response.json();
3608
+ return {
3609
+ sandboxId: data.sandboxID,
3610
+ templateId: data.templateID,
3611
+ name: data.name,
3612
+ metadata: (_a3 = data.metadata) != null ? _a3 : {},
3613
+ startedAt: new Date(data.startedAt),
3614
+ endAt: new Date(data.endAt),
3615
+ state: data.state,
3616
+ cpuCount: data.cpuCount,
3617
+ memoryMB: data.memoryMB,
3618
+ envdVersion: data.envdVersion,
3619
+ volumeMounts: (_b = data.volumeMounts) != null ? _b : []
3620
+ };
3469
3621
  }
3470
- static async getFullInfo(sandboxId, opts) {
3622
+ static async setNetwork(sandboxId, opts) {
3623
+ const config = new ConnectionConfig(opts);
3624
+ raiseIfLegacy(config, "Sandbox.setNetwork", SandboxError);
3625
+ const response = await fetch(
3626
+ `${config.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/network`,
3627
+ {
3628
+ method: "PUT",
3629
+ headers: getRawApiHeaders(config, {
3630
+ "Content-Type": "application/json"
3631
+ }),
3632
+ body: JSON.stringify(__spreadValues(__spreadValues({}, (opts == null ? void 0 : opts.allowOut) ? { allowOut: opts.allowOut } : {}), (opts == null ? void 0 : opts.denyOut) ? { denyOut: opts.denyOut } : {})),
3633
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
3634
+ }
3635
+ );
3636
+ if (response.status === 404) {
3637
+ throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
3638
+ }
3639
+ if (!response.ok) {
3640
+ throw await createRawApiError(response);
3641
+ }
3642
+ }
3643
+ static async cloneSandboxes(sandboxId, opts) {
3471
3644
  var _a3, _b, _c, _d;
3472
3645
  const config = new ConnectionConfig(opts);
3646
+ const response = await fetch(
3647
+ `${config.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/clone`,
3648
+ {
3649
+ method: "POST",
3650
+ headers: getRawApiHeaders(config, {
3651
+ "Content-Type": "application/json"
3652
+ }),
3653
+ body: JSON.stringify(__spreadValues(__spreadValues({
3654
+ count: (_a3 = opts == null ? void 0 : opts.count) != null ? _a3 : 1,
3655
+ strict: (_b = opts == null ? void 0 : opts.strict) != null ? _b : false
3656
+ }, (opts == null ? void 0 : opts.nodeId) ? { nodeID: opts.nodeId } : {}), (opts == null ? void 0 : opts.timeoutMs) !== void 0 ? { timeout: timeoutToSeconds(opts.timeoutMs) } : {})),
3657
+ signal: config.getSignal((_c = opts == null ? void 0 : opts.requestTimeoutMs) != null ? _c : 3e5)
3658
+ }
3659
+ );
3660
+ if (response.status === 404) {
3661
+ throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
3662
+ }
3663
+ if (!response.ok) {
3664
+ throw await createRawApiError(response);
3665
+ }
3666
+ const data = await response.json();
3667
+ return {
3668
+ sandboxes: ((_d = data.sandboxes) != null ? _d : []).map((sandbox) => {
3669
+ var _a4, _b2;
3670
+ const baseSandboxId = (_a4 = sandbox.sandboxID) != null ? _a4 : "";
3671
+ const sandboxId2 = sandbox.clientID ? `${baseSandboxId}-${sandbox.clientID}` : baseSandboxId;
3672
+ return {
3673
+ sandboxId: sandboxId2,
3674
+ envdVersion: (_b2 = sandbox.envdVersion) != null ? _b2 : "0.0.0",
3675
+ envdAccessToken: sandbox.envdAccessToken,
3676
+ trafficAccessToken: sandbox.trafficAccessToken
3677
+ };
3678
+ }),
3679
+ snapshotTemplateId: data.snapshotTemplateId
3680
+ };
3681
+ }
3682
+ static async getFullInfo(sandboxId, opts) {
3683
+ var _a3, _b, _c, _d, _e, _f;
3684
+ const config = new ConnectionConfig(opts);
3473
3685
  const client = new ApiClient(config);
3474
3686
  const res = await client.api.GET("/sandboxes/{sandboxID}", {
3475
3687
  params: {
@@ -3489,12 +3701,29 @@ var SandboxApi = class {
3489
3701
  if (!res.data) {
3490
3702
  throw new Error("Sandbox not found");
3491
3703
  }
3704
+ if (config.isLegacyDomain) {
3705
+ const data = res.data;
3706
+ return __spreadProps(__spreadValues({
3707
+ sandboxId: data.clientID != null ? `${data.sandboxID}-${data.clientID}` : data.sandboxID,
3708
+ templateId: data.templateID
3709
+ }, data.alias && { name: data.alias }), {
3710
+ metadata: (_b = data.metadata) != null ? _b : {},
3711
+ envdVersion: (_c = data.envdVersion) != null ? _c : "0.0.0",
3712
+ envdAccessToken: data.envdAccessToken,
3713
+ startedAt: new Date(data.startedAt),
3714
+ endAt: new Date(data.endAt),
3715
+ state: data.state,
3716
+ cpuCount: data.cpuCount,
3717
+ memoryMB: data.memoryMB,
3718
+ sandboxDomain: data.domain || void 0
3719
+ });
3720
+ }
3492
3721
  return __spreadProps(__spreadValues({
3493
3722
  sandboxId: res.data.sandboxID,
3494
3723
  templateId: res.data.templateID
3495
3724
  }, res.data.alias && { name: res.data.alias }), {
3496
- metadata: (_b = res.data.metadata) != null ? _b : {},
3497
- allowInternetAccess: (_c = res.data.allowInternetAccess) != null ? _c : void 0,
3725
+ metadata: (_d = res.data.metadata) != null ? _d : {},
3726
+ allowInternetAccess: (_e = res.data.allowInternetAccess) != null ? _e : void 0,
3498
3727
  envdVersion: res.data.envdVersion,
3499
3728
  envdAccessToken: res.data.envdAccessToken,
3500
3729
  startedAt: new Date(res.data.startedAt),
@@ -3513,7 +3742,7 @@ var SandboxApi = class {
3513
3742
  autoResume: res.data.lifecycle.autoResume
3514
3743
  } : void 0,
3515
3744
  sandboxDomain: res.data.domain || void 0,
3516
- volumeMounts: (_d = res.data.volumeMounts) != null ? _d : []
3745
+ volumeMounts: (_f = res.data.volumeMounts) != null ? _f : []
3517
3746
  });
3518
3747
  }
3519
3748
  /**
@@ -3525,8 +3754,31 @@ var SandboxApi = class {
3525
3754
  * @returns `true` if the sandbox got paused, `false` if the sandbox was already paused.
3526
3755
  */
3527
3756
  static async pause(sandboxId, opts) {
3528
- var _a3, _b;
3757
+ var _a3, _b, _c;
3529
3758
  const config = new ConnectionConfig(opts);
3759
+ if (config.isLegacyDomain) {
3760
+ const response = await fetch(
3761
+ `${config.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/pause`,
3762
+ {
3763
+ method: "POST",
3764
+ headers: getRawApiHeaders(config, {
3765
+ "Content-Type": "application/json"
3766
+ }),
3767
+ body: JSON.stringify({ sync: (_a3 = opts == null ? void 0 : opts.sync) != null ? _a3 : false }),
3768
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
3769
+ }
3770
+ );
3771
+ if (response.status === 404) {
3772
+ throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
3773
+ }
3774
+ if (response.status === 409) {
3775
+ return false;
3776
+ }
3777
+ if (!response.ok) {
3778
+ throw await createRawApiError(response);
3779
+ }
3780
+ return true;
3781
+ }
3530
3782
  const client = new ApiClient(config);
3531
3783
  const res = await client.api.POST("/sandboxes/{sandboxID}/pause", {
3532
3784
  params: {
@@ -3536,10 +3788,10 @@ var SandboxApi = class {
3536
3788
  },
3537
3789
  signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
3538
3790
  });
3539
- if (((_a3 = res.error) == null ? void 0 : _a3.code) === 404) {
3791
+ if (((_b = res.error) == null ? void 0 : _b.code) === 404) {
3540
3792
  throw new SandboxNotFoundError(`Sandbox ${sandboxId} not found`);
3541
3793
  }
3542
- if (((_b = res.error) == null ? void 0 : _b.code) === 409) {
3794
+ if (((_c = res.error) == null ? void 0 : _c.code) === 409) {
3543
3795
  return false;
3544
3796
  }
3545
3797
  const err = handleApiError(res);
@@ -3569,6 +3821,7 @@ var SandboxApi = class {
3569
3821
  static async createSnapshot(sandboxId, opts) {
3570
3822
  var _a3;
3571
3823
  const config = new ConnectionConfig(opts);
3824
+ raiseIfLegacy(config, "Sandbox.createSnapshot", SandboxError);
3572
3825
  const client = new ApiClient(config);
3573
3826
  const res = await client.api.POST("/sandboxes/{sandboxID}/snapshots", {
3574
3827
  params: {
@@ -3630,22 +3883,69 @@ var SandboxApi = class {
3630
3883
  return true;
3631
3884
  }
3632
3885
  static async createSandbox(template, timeoutMs, opts) {
3633
- var _a3, _b, _c;
3886
+ var _a3, _b, _c, _d, _e, _f;
3634
3887
  const config = new ConnectionConfig(opts);
3635
- const client = new ApiClient(config);
3636
3888
  const lifecycle = getLifecycle(opts);
3637
3889
  const autoPause = lifecycle.onTimeout === "pause";
3638
3890
  const autoResumeEnabled = lifecycle.onTimeout === "pause" ? (_a3 = lifecycle.autoResume) != null ? _a3 : false : void 0;
3639
- const body = __spreadValues(__spreadValues({
3891
+ if (config.isLegacyDomain) {
3892
+ if (opts == null ? void 0 : opts.mcp) {
3893
+ raiseIfLegacy(config, "Sandbox.create mcp", SandboxError);
3894
+ }
3895
+ if (opts == null ? void 0 : opts.network) {
3896
+ raiseIfLegacy(config, "Sandbox.create network", SandboxError);
3897
+ }
3898
+ if (opts == null ? void 0 : opts.volumeMounts) {
3899
+ raiseIfLegacy(config, "Sandbox.create volumeMounts", SandboxError);
3900
+ }
3901
+ if (opts == null ? void 0 : opts.secretEnvs) {
3902
+ raiseIfLegacy(config, "Sandbox.create secretEnvs", SandboxError);
3903
+ }
3904
+ const body2 = __spreadValues({
3905
+ templateID: template,
3906
+ timeout: timeoutToSeconds(timeoutMs),
3907
+ metadata: (_b = opts == null ? void 0 : opts.metadata) != null ? _b : {},
3908
+ envVars: (_c = opts == null ? void 0 : opts.envs) != null ? _c : {},
3909
+ secure: (_d = opts == null ? void 0 : opts.secure) != null ? _d : false,
3910
+ autoPause: autoPause != null ? autoPause : false
3911
+ }, (opts == null ? void 0 : opts.nodeId) ? { nodeID: opts.nodeId } : {});
3912
+ const response = await fetch(`${config.apiUrl}/sandboxes`, {
3913
+ method: "POST",
3914
+ headers: getRawApiHeaders(config, {
3915
+ "Content-Type": "application/json"
3916
+ }),
3917
+ body: JSON.stringify(body2),
3918
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
3919
+ });
3920
+ if (!response.ok) {
3921
+ throw await createRawApiError(response);
3922
+ }
3923
+ const data = await response.json();
3924
+ if ((0, import_compare_versions4.compareVersions)(data.envdVersion, "0.1.0") < 0) {
3925
+ await this.kill(data.sandboxID, opts);
3926
+ throw new TemplateError(
3927
+ "You need to update the template to use the new SDK. You can do this by running `ppio-sandbox template build` in the directory with the template."
3928
+ );
3929
+ }
3930
+ return {
3931
+ sandboxId: data.clientID ? `${data.sandboxID}-${data.clientID}` : data.sandboxID,
3932
+ sandboxDomain: data.domain || void 0,
3933
+ envdVersion: data.envdVersion,
3934
+ envdAccessToken: data.envdAccessToken,
3935
+ trafficAccessToken: void 0
3936
+ };
3937
+ }
3938
+ const client = new ApiClient(config);
3939
+ const body = __spreadValues(__spreadValues(__spreadValues({
3640
3940
  templateID: template,
3641
3941
  metadata: opts == null ? void 0 : opts.metadata,
3642
3942
  mcp: opts == null ? void 0 : opts.mcp,
3643
3943
  envVars: opts == null ? void 0 : opts.envs,
3644
3944
  timeout: timeoutToSeconds(timeoutMs),
3645
- secure: (_b = opts == null ? void 0 : opts.secure) != null ? _b : true,
3646
- allow_internet_access: (_c = opts == null ? void 0 : opts.allowInternetAccess) != null ? _c : true,
3945
+ secure: (_e = opts == null ? void 0 : opts.secure) != null ? _e : true,
3946
+ allow_internet_access: (_f = opts == null ? void 0 : opts.allowInternetAccess) != null ? _f : true,
3647
3947
  network: opts == null ? void 0 : opts.network
3648
- }, autoPause !== void 0 ? { autoPause } : {}), autoResumeEnabled !== void 0 ? { autoResume: { enabled: autoResumeEnabled } } : {});
3948
+ }, (opts == null ? void 0 : opts.nodeId) ? { nodeID: opts.nodeId } : {}), autoPause !== void 0 ? { autoPause } : {}), autoResumeEnabled !== void 0 ? { autoResume: { enabled: autoResumeEnabled } } : {});
3649
3949
  if (opts == null ? void 0 : opts.volumeMounts) {
3650
3950
  body.volumeMounts = Object.entries(opts.volumeMounts).map(
3651
3951
  ([mountPath, vol]) => ({
@@ -3654,6 +3954,14 @@ var SandboxApi = class {
3654
3954
  })
3655
3955
  );
3656
3956
  }
3957
+ const validatedSecretEnvs = validateSecretEnvs(
3958
+ opts == null ? void 0 : opts.secretEnvs,
3959
+ opts == null ? void 0 : opts.envs
3960
+ );
3961
+ if (Object.keys(validatedSecretEnvs).length > 0) {
3962
+ ;
3963
+ body.secrets = validatedSecretEnvs;
3964
+ }
3657
3965
  const res = await client.api.POST("/sandboxes", {
3658
3966
  body,
3659
3967
  signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
@@ -3680,6 +3988,33 @@ var SandboxApi = class {
3680
3988
  var _a3, _b;
3681
3989
  const timeoutMs = (_a3 = opts == null ? void 0 : opts.timeoutMs) != null ? _a3 : DEFAULT_SANDBOX_TIMEOUT_MS;
3682
3990
  const config = new ConnectionConfig(opts);
3991
+ if (config.isLegacyDomain) {
3992
+ const response = await fetch(
3993
+ `${config.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/connect`,
3994
+ {
3995
+ method: "POST",
3996
+ headers: getRawApiHeaders(config, {
3997
+ "Content-Type": "application/json"
3998
+ }),
3999
+ body: JSON.stringify({ timeout: timeoutToSeconds(timeoutMs) }),
4000
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
4001
+ }
4002
+ );
4003
+ if (response.status === 404) {
4004
+ throw new SandboxNotFoundError(`Paused sandbox ${sandboxId} not found`);
4005
+ }
4006
+ if (!response.ok) {
4007
+ throw await createRawApiError(response);
4008
+ }
4009
+ const data = await response.json();
4010
+ return {
4011
+ sandboxId: data.clientID ? `${data.sandboxID}-${data.clientID}` : data.sandboxID,
4012
+ sandboxDomain: data.domain || void 0,
4013
+ envdVersion: data.envdVersion || "0.0.0",
4014
+ envdAccessToken: data.envdAccessToken,
4015
+ trafficAccessToken: void 0
4016
+ };
4017
+ }
3683
4018
  const client = new ApiClient(config);
3684
4019
  const res = await client.api.POST("/sandboxes/{sandboxID}/connect", {
3685
4020
  params: {
@@ -3707,6 +4042,69 @@ var SandboxApi = class {
3707
4042
  trafficAccessToken: res.data.trafficAccessToken || void 0
3708
4043
  };
3709
4044
  }
4045
+ static async getQuota(opts) {
4046
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
4047
+ const config = new ConnectionConfig(opts);
4048
+ const url = new URL(`${config.apiUrl}/sandboxes/quota`);
4049
+ const response = await fetch(url, {
4050
+ method: "GET",
4051
+ headers: __spreadValues(__spreadValues(__spreadValues({}, config.headers), config.apiKey ? { "X-API-KEY": config.apiKey } : {}), config.accessToken ? { Authorization: `Bearer ${config.accessToken}` } : {}),
4052
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
4053
+ });
4054
+ if (!response.ok) {
4055
+ throw await createRawApiError(response);
4056
+ }
4057
+ const data = await response.json();
4058
+ return {
4059
+ limit: {
4060
+ concurrentInstances: (_b = (_a3 = data.limit) == null ? void 0 : _a3.concurrentInstances) != null ? _b : 0,
4061
+ concurrentVcpu: (_d = (_c = data.limit) == null ? void 0 : _c.concurrentVcpu) != null ? _d : 0,
4062
+ concurrentRamMb: (_f = (_e = data.limit) == null ? void 0 : _e.concurrentRamMb) != null ? _f : 0,
4063
+ maxLengthHours: (_h = (_g = data.limit) == null ? void 0 : _g.maxLengthHours) != null ? _h : 0,
4064
+ diskMb: (_j = (_i = data.limit) == null ? void 0 : _i.diskMb) != null ? _j : 0,
4065
+ maxVcpu: (_l = (_k = data.limit) == null ? void 0 : _k.maxVcpu) != null ? _l : 0,
4066
+ maxRamMb: (_n = (_m = data.limit) == null ? void 0 : _m.maxRamMb) != null ? _n : 0
4067
+ },
4068
+ usage: {
4069
+ concurrentInstances: (_p = (_o = data.usage) == null ? void 0 : _o.concurrentInstances) != null ? _p : 0,
4070
+ concurrentVcpu: (_r = (_q = data.usage) == null ? void 0 : _q.concurrentVcpu) != null ? _r : 0,
4071
+ concurrentRamMb: (_t = (_s = data.usage) == null ? void 0 : _s.concurrentRamMb) != null ? _t : 0
4072
+ }
4073
+ };
4074
+ }
4075
+ static async getEvents(opts) {
4076
+ var _a3, _b, _c;
4077
+ const config = new ConnectionConfig(opts);
4078
+ const url = new URL(`${config.apiUrl}/sandboxes/events`);
4079
+ if ((opts == null ? void 0 : opts.startTime) !== void 0)
4080
+ url.searchParams.set("startTime", String(opts.startTime));
4081
+ if ((opts == null ? void 0 : opts.endTime) !== void 0)
4082
+ url.searchParams.set("endTime", String(opts.endTime));
4083
+ if (opts == null ? void 0 : opts.sandboxID) url.searchParams.set("sandboxID", opts.sandboxID);
4084
+ if (opts == null ? void 0 : opts.templateID) url.searchParams.set("templateID", opts.templateID);
4085
+ if (opts == null ? void 0 : opts.events) url.searchParams.set("events", opts.events);
4086
+ if (opts == null ? void 0 : opts.state) url.searchParams.set("state", opts.state);
4087
+ if ((opts == null ? void 0 : opts.orderAsc) !== void 0)
4088
+ url.searchParams.set("orderAsc", String(opts.orderAsc));
4089
+ if ((opts == null ? void 0 : opts.offset) !== void 0)
4090
+ url.searchParams.set("offset", String(opts.offset));
4091
+ if ((opts == null ? void 0 : opts.limit) !== void 0)
4092
+ url.searchParams.set("limit", String(opts.limit));
4093
+ const response = await fetch(url, {
4094
+ method: "GET",
4095
+ headers: __spreadValues(__spreadValues(__spreadValues({}, config.headers), config.apiKey ? { "X-API-KEY": config.apiKey } : {}), config.accessToken ? { Authorization: `Bearer ${config.accessToken}` } : {}),
4096
+ signal: config.getSignal(opts == null ? void 0 : opts.requestTimeoutMs)
4097
+ });
4098
+ if (!response.ok) {
4099
+ throw await createRawApiError(response);
4100
+ }
4101
+ const data = await response.json();
4102
+ return {
4103
+ items: (_a3 = data.items) != null ? _a3 : [],
4104
+ total: (_b = data.total) != null ? _b : 0,
4105
+ hasMore: (_c = data.hasMore) != null ? _c : false
4106
+ };
4107
+ }
3710
4108
  };
3711
4109
  var BasePaginator = class {
3712
4110
  constructor(config, limit, nextToken) {
@@ -3739,7 +4137,7 @@ var SandboxPaginator = class extends BasePaginator {
3739
4137
  this.query = opts == null ? void 0 : opts.query;
3740
4138
  }
3741
4139
  async nextItems() {
3742
- var _a3, _b, _c;
4140
+ var _a3, _b, _c, _d, _e;
3743
4141
  if (!this.hasNext) {
3744
4142
  throw new Error("No more items to fetch");
3745
4143
  }
@@ -3753,11 +4151,55 @@ var SandboxPaginator = class extends BasePaginator {
3753
4151
  );
3754
4152
  metadata = new URLSearchParams(encodedPairs).toString();
3755
4153
  }
4154
+ if (this.config.isLegacyDomain) {
4155
+ const params = new URLSearchParams();
4156
+ if (metadata) {
4157
+ params.set("metadata", metadata);
4158
+ }
4159
+ if ((_c = (_b = this.query) == null ? void 0 : _b.state) == null ? void 0 : _c.length) {
4160
+ params.set("state", this.query.state.join(","));
4161
+ }
4162
+ if (this.limit) {
4163
+ params.set("limit", String(this.limit));
4164
+ }
4165
+ if (this.nextToken) {
4166
+ params.set("nextToken", this.nextToken);
4167
+ }
4168
+ const query = params.toString();
4169
+ const response = await fetch(
4170
+ `${this.config.apiUrl}/v2/sandboxes${query ? `?${query}` : ""}`,
4171
+ {
4172
+ headers: getRawApiHeaders(this.config),
4173
+ signal: this.config.getSignal()
4174
+ }
4175
+ );
4176
+ if (!response.ok) {
4177
+ throw await createRawApiError(response);
4178
+ }
4179
+ this.updatePagination(response);
4180
+ const data = await response.json();
4181
+ return (data != null ? data : []).map((sandbox) => {
4182
+ var _a4, _b2;
4183
+ return __spreadProps(__spreadValues({
4184
+ sandboxId: sandbox.clientID != null ? `${sandbox.sandboxID}-${sandbox.clientID}` : sandbox.sandboxID,
4185
+ templateId: sandbox.templateID
4186
+ }, sandbox.alias && { name: sandbox.alias }), {
4187
+ metadata: (_a4 = sandbox.metadata) != null ? _a4 : {},
4188
+ startedAt: new Date(sandbox.startedAt),
4189
+ endAt: new Date(sandbox.endAt),
4190
+ state: sandbox.state,
4191
+ cpuCount: sandbox.cpuCount,
4192
+ memoryMB: sandbox.memoryMB,
4193
+ envdVersion: sandbox.envdVersion,
4194
+ volumeMounts: (_b2 = sandbox.volumeMounts) != null ? _b2 : []
4195
+ });
4196
+ });
4197
+ }
3756
4198
  const res = await this.client.api.GET("/v2/sandboxes", {
3757
4199
  params: {
3758
4200
  query: {
3759
4201
  metadata,
3760
- state: (_b = this.query) == null ? void 0 : _b.state,
4202
+ state: (_d = this.query) == null ? void 0 : _d.state,
3761
4203
  limit: this.limit,
3762
4204
  nextToken: this.nextToken
3763
4205
  }
@@ -3770,11 +4212,11 @@ var SandboxPaginator = class extends BasePaginator {
3770
4212
  throw err;
3771
4213
  }
3772
4214
  this.updatePagination(res.response);
3773
- return ((_c = res.data) != null ? _c : []).map(
4215
+ return ((_e = res.data) != null ? _e : []).map(
3774
4216
  (sandbox) => {
3775
4217
  var _a4, _b2;
3776
4218
  return __spreadProps(__spreadValues({
3777
- sandboxId: sandbox.sandboxID,
4219
+ sandboxId: this.config.isLegacyDomain && sandbox.clientID != null ? `${sandbox.sandboxID}-${sandbox.clientID}` : sandbox.sandboxID,
3778
4220
  templateId: sandbox.templateID
3779
4221
  }, sandbox.alias && { name: sandbox.alias }), {
3780
4222
  metadata: (_a4 = sandbox.metadata) != null ? _a4 : {},
@@ -3793,6 +4235,7 @@ var SandboxPaginator = class extends BasePaginator {
3793
4235
  var SnapshotPaginator = class extends BasePaginator {
3794
4236
  constructor(opts) {
3795
4237
  super(new ConnectionConfig(opts), opts == null ? void 0 : opts.limit, opts == null ? void 0 : opts.nextToken);
4238
+ raiseIfLegacy(this.config, "Sandbox.listSnapshots", SandboxError);
3796
4239
  this.sandboxId = opts == null ? void 0 : opts.sandboxId;
3797
4240
  }
3798
4241
  async nextItems() {
@@ -3932,7 +4375,7 @@ var Sandbox = class extends SandboxApi {
3932
4375
  sandboxOpts
3933
4376
  );
3934
4377
  const sandbox = new this(__spreadValues(__spreadValues({}, sandboxInfo), config));
3935
- if (sandboxOpts == null ? void 0 : sandboxOpts.mcp) {
4378
+ if ((sandboxOpts == null ? void 0 : sandboxOpts.mcp) && !config.isLegacyDomain) {
3936
4379
  sandbox.mcpToken = crypto.randomUUID();
3937
4380
  const res = await sandbox.commands.run(
3938
4381
  `mcp-gateway --config ${shellQuote(JSON.stringify(sandboxOpts.mcp))}`,
@@ -3949,6 +4392,101 @@ var Sandbox = class extends SandboxApi {
3949
4392
  }
3950
4393
  return sandbox;
3951
4394
  }
4395
+ /**
4396
+ * Clone a sandbox to create one or more identical sandboxes.
4397
+ *
4398
+ * @param sandboxId source sandbox ID.
4399
+ * @param opts clone options.
4400
+ *
4401
+ * @returns cloned sandbox instances plus snapshot template ID.
4402
+ */
4403
+ static async clone(sandboxId, opts) {
4404
+ const cloneResult = await SandboxApi.cloneSandboxes(sandboxId, opts);
4405
+ const config = new ConnectionConfig(opts);
4406
+ const sandboxes = cloneResult.sandboxes.map(
4407
+ (sandbox) => new this(__spreadValues({
4408
+ sandboxId: sandbox.sandboxId,
4409
+ envdVersion: sandbox.envdVersion,
4410
+ envdAccessToken: sandbox.envdAccessToken,
4411
+ trafficAccessToken: sandbox.trafficAccessToken
4412
+ }, config))
4413
+ );
4414
+ return {
4415
+ sandboxes,
4416
+ count: sandboxes.length,
4417
+ snapshotTemplateId: cloneResult.snapshotTemplateId
4418
+ };
4419
+ }
4420
+ /**
4421
+ * Dynamically adjust the hotplugged memory on a running sandbox.
4422
+ *
4423
+ * The value represents the new *total* plugged size in MiB, not a delta.
4424
+ * Use `0` to unplug all hotplugged memory.
4425
+ */
4426
+ static async hotplugMemory(sandboxId, requestedSizeMib, opts) {
4427
+ return await SandboxApi.hotplugMemory(
4428
+ sandboxId,
4429
+ requestedSizeMib,
4430
+ opts
4431
+ );
4432
+ }
4433
+ /**
4434
+ * Mount a volume to a sandbox.
4435
+ *
4436
+ * @param sandboxId sandbox ID.
4437
+ * @param name volume name.
4438
+ * @param path mount path inside the sandbox.
4439
+ * @param opts connection options.
4440
+ *
4441
+ * @returns updated sandbox information.
4442
+ */
4443
+ static async mountVolume(sandboxId, name, path2, opts) {
4444
+ return await SandboxApi.mountVolume(sandboxId, name, path2, opts);
4445
+ }
4446
+ /**
4447
+ * Unmount a volume from a sandbox.
4448
+ *
4449
+ * @param sandboxId sandbox ID.
4450
+ * @param path mount path to unmount.
4451
+ * @param opts connection options.
4452
+ * @param [opts.force] force unmount.
4453
+ *
4454
+ * @returns updated sandbox information.
4455
+ */
4456
+ static async unmountVolume(sandboxId, path2, opts) {
4457
+ return await SandboxApi.unmountVolume(sandboxId, path2, opts);
4458
+ }
4459
+ /**
4460
+ * Commit a sandbox to create a snapshot template.
4461
+ *
4462
+ * @deprecated Use snapshot APIs instead. This method is only supported for modern domains.
4463
+ *
4464
+ * @param sandboxId source sandbox ID.
4465
+ * @param opts commit options.
4466
+ *
4467
+ * @returns created snapshot template.
4468
+ */
4469
+ static async commit(sandboxId, opts) {
4470
+ return await SandboxApi.commit(sandboxId, opts);
4471
+ }
4472
+ /**
4473
+ * Reset sandbox memory and optionally resume the sandbox.
4474
+ *
4475
+ * @param sandboxId sandbox ID.
4476
+ * @param opts reset options.
4477
+ *
4478
+ * @returns `true` if reset succeeded, `false` if the sandbox is in a conflicting state.
4479
+ */
4480
+ static async reset(sandboxId, opts) {
4481
+ return await SandboxApi.reset(sandboxId, opts);
4482
+ }
4483
+ /**
4484
+ * Update network egress rules for a running sandbox.
4485
+ * Omitting both `allowOut` and `denyOut` clears all egress rules.
4486
+ */
4487
+ static async setNetwork(sandboxId, opts) {
4488
+ await SandboxApi.setNetwork(sandboxId, opts);
4489
+ }
3952
4490
  static async betaCreate(templateOrOpts, opts) {
3953
4491
  var _a3, _b, _c;
3954
4492
  const { template, sandboxOpts } = typeof templateOrOpts === "string" ? {
@@ -3971,7 +4509,7 @@ var Sandbox = class extends SandboxApi {
3971
4509
  sandboxOpts
3972
4510
  );
3973
4511
  const sandbox = new this(__spreadValues(__spreadValues({}, sandboxInfo), config));
3974
- if (sandboxOpts == null ? void 0 : sandboxOpts.mcp) {
4512
+ if ((sandboxOpts == null ? void 0 : sandboxOpts.mcp) && !config.isLegacyDomain) {
3975
4513
  sandbox.mcpToken = crypto.randomUUID();
3976
4514
  const res = await sandbox.commands.run(
3977
4515
  `mcp-gateway --config ${shellQuote(JSON.stringify(sandboxOpts.mcp))}`,
@@ -4113,6 +4651,68 @@ var Sandbox = class extends SandboxApi {
4113
4651
  this.resolveApiOpts(opts)
4114
4652
  );
4115
4653
  }
4654
+ /**
4655
+ * Reset sandbox memory and optionally resume the sandbox.
4656
+ *
4657
+ * @param opts reset options.
4658
+ *
4659
+ * @returns `true` if reset succeeded, `false` if the sandbox is in a conflicting state.
4660
+ */
4661
+ async reset(opts) {
4662
+ return await SandboxApi.reset(this.sandboxId, this.resolveApiOpts(opts));
4663
+ }
4664
+ /**
4665
+ * Update network egress rules for this running sandbox.
4666
+ * Omitting both `allowOut` and `denyOut` clears all egress rules.
4667
+ */
4668
+ async setNetwork(opts) {
4669
+ await SandboxApi.setNetwork(this.sandboxId, this.resolveApiOpts(opts));
4670
+ }
4671
+ /**
4672
+ * Commit this sandbox to create a snapshot template.
4673
+ *
4674
+ * @deprecated Use snapshot APIs instead. This method is only supported for modern domains.
4675
+ *
4676
+ * @param opts commit options.
4677
+ *
4678
+ * @returns created snapshot template.
4679
+ */
4680
+ async commit(opts) {
4681
+ return await SandboxApi.commit(this.sandboxId, this.resolveApiOpts(opts));
4682
+ }
4683
+ /**
4684
+ * Mount a volume to this sandbox.
4685
+ *
4686
+ * @param name volume name.
4687
+ * @param path mount path inside the sandbox.
4688
+ * @param opts connection options.
4689
+ *
4690
+ * @returns updated sandbox information.
4691
+ */
4692
+ async mountVolume(name, path2, opts) {
4693
+ return await SandboxApi.mountVolume(
4694
+ this.sandboxId,
4695
+ name,
4696
+ path2,
4697
+ this.resolveApiOpts(opts)
4698
+ );
4699
+ }
4700
+ /**
4701
+ * Unmount a volume from this sandbox.
4702
+ *
4703
+ * @param path mount path to unmount.
4704
+ * @param opts connection options.
4705
+ * @param [opts.force] force unmount.
4706
+ *
4707
+ * @returns updated sandbox information.
4708
+ */
4709
+ async unmountVolume(path2, opts) {
4710
+ return await SandboxApi.unmountVolume(
4711
+ this.sandboxId,
4712
+ path2,
4713
+ this.resolveApiOpts(opts)
4714
+ );
4715
+ }
4116
4716
  /**
4117
4717
  * Kill the sandbox.
4118
4718
  *
@@ -4124,6 +4724,22 @@ var Sandbox = class extends SandboxApi {
4124
4724
  }
4125
4725
  await SandboxApi.kill(this.sandboxId, this.resolveApiOpts(opts));
4126
4726
  }
4727
+ /**
4728
+ * Dynamically adjust the hotplugged memory on a running sandbox.
4729
+ *
4730
+ * The value represents the new *total* plugged size in MiB, not a delta.
4731
+ * Use `0` to unplug all hotplugged memory.
4732
+ *
4733
+ * @param requestedSizeMib new total plugged memory size in MiB.
4734
+ * @param opts connection options.
4735
+ */
4736
+ async hotplugMemory(requestedSizeMib, opts) {
4737
+ await SandboxApi.hotplugMemory(
4738
+ this.sandboxId,
4739
+ requestedSizeMib,
4740
+ this.resolveApiOpts(opts)
4741
+ );
4742
+ }
4127
4743
  /**
4128
4744
  * Pause a sandbox by its ID.
4129
4745
  *
@@ -4144,7 +4760,10 @@ var Sandbox = class extends SandboxApi {
4144
4760
  * @deprecated Use {@link Sandbox.pause} instead.
4145
4761
  */
4146
4762
  async betaPause(opts) {
4147
- return await SandboxApi.betaPause(this.sandboxId, this.resolveApiOpts(opts));
4763
+ return await SandboxApi.betaPause(
4764
+ this.sandboxId,
4765
+ this.resolveApiOpts(opts)
4766
+ );
4148
4767
  }
4149
4768
  /**
4150
4769
  * Create a snapshot of the sandbox's current state.
@@ -4196,6 +4815,7 @@ var Sandbox = class extends SandboxApi {
4196
4815
  * @returns MCP URL for the sandbox.
4197
4816
  */
4198
4817
  getMcpUrl() {
4818
+ raiseIfLegacy(this.connectionConfig, "Sandbox.getMcpUrl", SandboxError);
4199
4819
  return `https://${this.getHost(this.mcpPort)}/mcp`;
4200
4820
  }
4201
4821
  /**
@@ -4204,6 +4824,7 @@ var Sandbox = class extends SandboxApi {
4204
4824
  * @returns MCP token for the sandbox, or undefined if MCP is not enabled.
4205
4825
  */
4206
4826
  async getMcpToken() {
4827
+ raiseIfLegacy(this.connectionConfig, "Sandbox.getMcpToken", SandboxError);
4207
4828
  if (!this.mcpToken) {
4208
4829
  this.mcpToken = await this.files.read("/etc/mcp-gateway/.token", {
4209
4830
  user: "root"
@@ -5157,6 +5778,123 @@ function handleCmdEntrypointInstruction(instruction, templateBuilder) {
5157
5778
  }
5158
5779
 
5159
5780
  // src/core/template/index.ts
5781
+ var TemplateApi = class {
5782
+ static async listTemplates(options) {
5783
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
5784
+ const templateType = (_a3 = options == null ? void 0 : options.templateType) != null ? _a3 : "template_build";
5785
+ const page = (_b = options == null ? void 0 : options.page) != null ? _b : 1;
5786
+ const limit = (_c = options == null ? void 0 : options.limit) != null ? _c : 20;
5787
+ if (!["template_build", "snapshot_template"].includes(templateType)) {
5788
+ throw new Error(
5789
+ 'templateType must be either "template_build" or "snapshot_template"'
5790
+ );
5791
+ }
5792
+ if (page < 1) {
5793
+ throw new Error("page must be >= 1");
5794
+ }
5795
+ if (limit < 1 || limit > 100) {
5796
+ throw new Error("limit must be between 1 and 100");
5797
+ }
5798
+ const config = new ConnectionConfig(options);
5799
+ const url = new URL(`${config.apiUrl}/v2/templates`);
5800
+ url.searchParams.set("template_type", templateType);
5801
+ url.searchParams.set("page", String(page));
5802
+ url.searchParams.set("limit", String(limit));
5803
+ const response = await fetch(url, {
5804
+ method: "GET",
5805
+ headers: __spreadValues(__spreadValues(__spreadValues({}, config.headers), config.apiKey ? { "X-API-KEY": config.apiKey } : {}), config.accessToken ? { Authorization: `Bearer ${config.accessToken}` } : {}),
5806
+ signal: config.getSignal(options == null ? void 0 : options.requestTimeoutMs)
5807
+ });
5808
+ if (!response.ok) {
5809
+ throw new Error(`${response.status}: ${await response.text()}`);
5810
+ }
5811
+ const data = await response.json();
5812
+ if (!data) {
5813
+ throw new Error("Template list response data is missing");
5814
+ }
5815
+ return {
5816
+ templates: ((_d = data.templates) != null ? _d : []).map(
5817
+ (template) => ({
5818
+ templateId: template.templateID,
5819
+ aliases: template.aliases,
5820
+ buildCount: template.buildCount,
5821
+ buildId: template.buildID,
5822
+ cpuCount: template.cpuCount,
5823
+ createdAt: new Date(template.createdAt),
5824
+ createdBy: template.createdBy,
5825
+ diskSizeMB: template.diskSizeMB,
5826
+ envdVersion: template.envdVersion,
5827
+ lastSpawnedAt: template.lastSpawnedAt ? new Date(template.lastSpawnedAt) : null,
5828
+ memoryMB: template.memoryMB,
5829
+ metadata: template.metadata,
5830
+ public: template.public,
5831
+ spawnCount: template.spawnCount,
5832
+ templateType: template.templateType,
5833
+ originSandboxID: template.originSandboxID,
5834
+ updatedAt: new Date(template.updatedAt)
5835
+ })
5836
+ ),
5837
+ page: (_e = data.page) != null ? _e : page,
5838
+ limit: (_f = data.limit) != null ? _f : limit,
5839
+ total: (_g = data.total) != null ? _g : 0,
5840
+ totalPages: (_h = data.totalPages) != null ? _h : 0
5841
+ };
5842
+ }
5843
+ static async deleteTemplate(templateId, options) {
5844
+ var _a3;
5845
+ if (!templateId) {
5846
+ throw new Error("templateId cannot be empty");
5847
+ }
5848
+ const config = new ConnectionConfig(options);
5849
+ const client = new ApiClient(config);
5850
+ const res = await client.api.DELETE("/templates/{templateID}", {
5851
+ params: {
5852
+ path: {
5853
+ templateID: templateId
5854
+ }
5855
+ },
5856
+ signal: config.getSignal(options == null ? void 0 : options.requestTimeoutMs)
5857
+ });
5858
+ if (((_a3 = res.error) == null ? void 0 : _a3.code) === 404 || res.response.status === 404) {
5859
+ return false;
5860
+ }
5861
+ const err = handleApiError(res, BuildError);
5862
+ if (err) {
5863
+ throw err;
5864
+ }
5865
+ return true;
5866
+ }
5867
+ };
5868
+ var TemplatePaginator = class {
5869
+ constructor(options) {
5870
+ this.hasNextPage = true;
5871
+ var _a3;
5872
+ this.options = options;
5873
+ this.nextPageNumber = (_a3 = options == null ? void 0 : options.page) != null ? _a3 : 1;
5874
+ }
5875
+ get hasNext() {
5876
+ return this.hasNextPage;
5877
+ }
5878
+ get currentPage() {
5879
+ return this.nextPageNumber;
5880
+ }
5881
+ async nextPage() {
5882
+ if (!this.hasNextPage) {
5883
+ throw new Error("No more pages to fetch");
5884
+ }
5885
+ const response = await TemplateApi.listTemplates(__spreadProps(__spreadValues({}, this.options), {
5886
+ page: this.nextPageNumber
5887
+ }));
5888
+ this.hasNextPage = response.page < response.totalPages;
5889
+ this.nextPageNumber = response.page + 1;
5890
+ return response;
5891
+ }
5892
+ reset() {
5893
+ var _a3, _b;
5894
+ this.nextPageNumber = (_b = (_a3 = this.options) == null ? void 0 : _a3.page) != null ? _b : 1;
5895
+ this.hasNextPage = true;
5896
+ }
5897
+ };
5160
5898
  var _a2;
5161
5899
  var TemplateBase = class _TemplateBase {
5162
5900
  constructor(options) {
@@ -5166,6 +5904,7 @@ var TemplateBase = class _TemplateBase {
5166
5904
  this.registryConfig = void 0;
5167
5905
  this.startCmd = void 0;
5168
5906
  this.readyCmd = void 0;
5907
+ this.patchCmd = void 0;
5169
5908
  // Force the whole template to be rebuilt
5170
5909
  this.force = false;
5171
5910
  // Force the next layer to be rebuilt
@@ -5181,6 +5920,9 @@ var TemplateBase = class _TemplateBase {
5181
5920
  this.fileContextPath = (_a3 = options == null ? void 0 : options.fileContextPath) != null ? _a3 : this.fileContextPath;
5182
5921
  this.fileIgnorePatterns = (_b = options == null ? void 0 : options.fileIgnorePatterns) != null ? _b : this.fileIgnorePatterns;
5183
5922
  }
5923
+ static list(options) {
5924
+ return new TemplatePaginator(options);
5925
+ }
5184
5926
  /**
5185
5927
  * Convert a template to JSON representation.
5186
5928
  *
@@ -5259,6 +6001,15 @@ var TemplateBase = class _TemplateBase {
5259
6001
  logsOffset: options == null ? void 0 : options.logsOffset
5260
6002
  });
5261
6003
  }
6004
+ static async listTemplates(options) {
6005
+ return TemplateApi.listTemplates(options);
6006
+ }
6007
+ static async delete(templateId, options) {
6008
+ await TemplateApi.deleteTemplate(templateId, options);
6009
+ }
6010
+ static async deleteTemplate(templateId, options) {
6011
+ return TemplateApi.deleteTemplate(templateId, options);
6012
+ }
5262
6013
  /**
5263
6014
  * Check if a template with the given name exists.
5264
6015
  *
@@ -5727,6 +6478,11 @@ var TemplateBase = class _TemplateBase {
5727
6478
  this.collectStackTrace();
5728
6479
  return this;
5729
6480
  }
6481
+ setPatchCmd(cmd) {
6482
+ this.patchCmd = cmd;
6483
+ this.collectStackTrace();
6484
+ return this;
6485
+ }
5730
6486
  setEnvs(envs) {
5731
6487
  if (Object.keys(envs).length === 0) {
5732
6488
  return this;
@@ -6052,6 +6808,7 @@ var TemplateBase = class _TemplateBase {
6052
6808
  const templateData = {
6053
6809
  startCmd: this.startCmd,
6054
6810
  readyCmd: this.readyCmd,
6811
+ preBootScript: this.patchCmd,
6055
6812
  steps,
6056
6813
  force: this.force
6057
6814
  };
@@ -6078,6 +6835,8 @@ Template.aliasExists = TemplateBase.aliasExists;
6078
6835
  Template.assignTags = TemplateBase.assignTags;
6079
6836
  Template.removeTags = TemplateBase.removeTags;
6080
6837
  Template.getTags = TemplateBase.getTags;
6838
+ Template.list = TemplateBase.list;
6839
+ Template.delete = TemplateBase.delete;
6081
6840
  Template.toJSON = TemplateBase.toJSON;
6082
6841
  Template.toDockerfile = TemplateBase.toDockerfile;
6083
6842
 
@@ -6626,13 +7385,16 @@ var VNCServer = class {
6626
7385
  Sandbox,
6627
7386
  SandboxError,
6628
7387
  SandboxNotFoundError,
7388
+ Secret,
7389
+ SecretError,
6629
7390
  Template,
7391
+ TemplateApi,
6630
7392
  TemplateBase,
6631
7393
  TemplateError,
7394
+ TemplatePaginator,
6632
7395
  TimeoutError,
6633
7396
  Volume,
6634
7397
  VolumeError,
6635
- VolumeFileType,
6636
7398
  defaultBuildLogger,
6637
7399
  getSignature,
6638
7400
  waitForFile,