@lizard-build/sdk 0.1.44 → 0.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,7 +79,7 @@ Pass `timeoutMs` to `create()` to set the sandbox lifetime. The default is five
79
79
 
80
80
  ## Runtime limits
81
81
 
82
- Sandboxes run on Kubernetes. Use volumes mounted at `/workspace` to keep files after a sandbox ends. Pause, resume, fork, snapshot creation, snapshot restore, and file watching return HTTP 501 on the current backend.
82
+ Sandboxes run on Kubernetes. Use volumes mounted at `/workspace` to keep files after a sandbox ends. CRIU pause/resume and private snapshots preserve running memory and workspace files. Snapshot pools keep five copies warm by default and can be paused to release idle compute. Fork and file watching remain unsupported. See the [snapshot lifecycle guide](../../docs/sandboxes.md#snapshots-and-criu-pause-resume).
83
83
 
84
84
  `getHost()` returns a hostname without `https://`. File writes accept UTF-8 text or valid UTF-8 bytes; `readBytes()` supports binary downloads.
85
85
 
package/dist/index.d.mts CHANGED
@@ -650,6 +650,9 @@ interface SandboxInfo {
650
650
  /** Region the sandbox runs in — the volume's region when one is attached. */
651
651
  region?: string;
652
652
  status?: string;
653
+ pauseError?: string | null;
654
+ size?: 'small' | 'medium' | 'large' | null;
655
+ pricePerHour?: number | null;
653
656
  cpus?: number;
654
657
  memoryMb?: number;
655
658
  metadata?: Record<string, string>;
@@ -665,6 +668,8 @@ interface SandboxOpts extends ConnectionOpts {
665
668
  /** Exact project ID — skips resolving {@link SandboxOpts.project}. */
666
669
  projectId?: string;
667
670
  template?: string;
671
+ snapshotId?: string;
672
+ size?: 'small' | 'medium' | 'large';
668
673
  metadata?: Record<string, string>;
669
674
  envs?: Record<string, string>;
670
675
  timeoutMs?: number;
@@ -731,6 +736,27 @@ declare class SandboxClient {
731
736
  }>;
732
737
  }
733
738
 
739
+ interface SandboxSnapshot {
740
+ id: string;
741
+ name: string;
742
+ projectId: string;
743
+ workspaceId: string;
744
+ region: string;
745
+ sourceSandboxId: string;
746
+ status: 'building' | 'warming' | 'ready' | 'paused' | 'suspended' | 'failed';
747
+ poolSize: number;
748
+ readyCount: number;
749
+ error: string | null;
750
+ cpus: number;
751
+ memoryMb: number;
752
+ createdAt: number;
753
+ }
754
+ interface SnapshotOpts {
755
+ poolSize?: number;
756
+ }
757
+ interface SnapshotWaitOpts extends ConnectionOpts {
758
+ waitTimeoutMs?: number;
759
+ }
734
760
  /**
735
761
  * A Linux sandbox running on Kubernetes.
736
762
  *
@@ -856,20 +882,12 @@ declare class Sandbox extends SandboxClient {
856
882
  * @returns `true` if the sandbox was terminated, `false` if it was already gone.
857
883
  */
858
884
  kill(opts?: ConnectionOpts): Promise<boolean>;
859
- /**
860
- * Request sandbox pause. The Kubernetes backend returns HTTP 501.
861
- *
862
- * @deprecated Unsupported on Kubernetes. Save files to a {@link Volume}
863
- * and attach it to a new sandbox instead.
864
- */
885
+ /** Queue a CRIU checkpoint and stop compute. Use waitForStatus('paused') before resuming. */
865
886
  pause(opts?: ConnectionOpts): Promise<boolean>;
866
- /**
867
- * Request sandbox resume. The Kubernetes backend returns HTTP 501.
868
- *
869
- * @deprecated Unsupported on Kubernetes. Use {@link Sandbox.connect} to
870
- * reconnect to a running sandbox.
871
- */
887
+ /** Queue restoration of saved memory and files. Use waitForStatus('running') before executing. */
872
888
  resume(opts?: ConnectionOpts): Promise<boolean>;
889
+ /** Wait for an asynchronous pause/resume operation to finish. */
890
+ waitForStatus(status: 'paused' | 'running', opts?: SnapshotWaitOpts): Promise<SandboxInfo>;
873
891
  /**
874
892
  * Get metadata and status information about this sandbox.
875
893
  */
@@ -899,18 +917,25 @@ declare class Sandbox extends SandboxClient {
899
917
  count?: number;
900
918
  timeoutMs?: number;
901
919
  }): Promise<unknown>;
902
- /** Snapshot creation is unsupported on Kubernetes; the backend returns HTTP 501. */
903
- snapshot(name?: string): Promise<unknown>;
920
+ /** Capture memory and workspace files. Disconnect active clients before capturing. */
921
+ snapshot(name?: string, opts?: SnapshotOpts): Promise<SandboxSnapshot>;
904
922
  unexpose(port: number): Promise<void>;
905
923
  logs(opts?: {
906
924
  tail?: number;
907
925
  signal?: AbortSignal;
908
926
  }): AsyncGenerator<StreamEvent, any, any>;
909
- static snapshots(projectId: string, opts?: ConnectionOpts): Promise<unknown[]>;
910
- /** Snapshot restore is unsupported on Kubernetes; the backend returns HTTP 501. */
927
+ static snapshots(projectId: string, opts?: ConnectionOpts): Promise<SandboxSnapshot[]>;
928
+ static getSnapshot(snapshotId: string, opts?: ConnectionOpts): Promise<SandboxSnapshot>;
929
+ /** Start a sandbox from an available warm copy in the snapshot's project and region. */
911
930
  static restore(snapshotId: string, opts?: ConnectionOpts & {
912
931
  timeoutMs?: number;
913
932
  }): Promise<Sandbox>;
933
+ static setSnapshotWarmPool(snapshotId: string, poolSize: number, opts?: ConnectionOpts): Promise<SandboxSnapshot>;
934
+ /** Release idle warm copies; retain saved state and existing claimed sandboxes. */
935
+ static pauseSnapshot(snapshotId: string, opts?: ConnectionOpts): Promise<SandboxSnapshot>;
936
+ /** Refill the configured warm pool without another capture. */
937
+ static resumeSnapshot(snapshotId: string, opts?: ConnectionOpts): Promise<SandboxSnapshot>;
938
+ static waitForSnapshot(snapshotId: string, opts?: SnapshotWaitOpts): Promise<SandboxSnapshot>;
914
939
  static deleteSnapshot(snapshotId: string, opts?: ConnectionOpts): Promise<void>;
915
940
  private resolveOpts;
916
941
  }
@@ -1773,4 +1798,4 @@ declare class LizardCLI {
1773
1798
  }): Promise<CLIResult>;
1774
1799
  }
1775
1800
 
1776
- export { type Account, type Addon, type AddonType, AddonsAPI, type ApiKey, type ApiKeyScope, ApiKeysAPI, AuthenticationError, type Balance, BillingAPI, type CLIOptions, type CLIResult, type CodeContext, CodeSandbox, ConfigApplyError, type ConfigResult, ConflictError, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateApiKeyOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type CreateWorkspaceOpts, type CreatedApiKey, type DeployEvent, DeployHandle, type DomainInfo, type DomainStatus, DomainsAPI, Execution, ExecutionError, type FileInfo, type FsOpts, GitHubAPI, type ListTransactionsOpts, Lizard, LizardCLI, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, MetricsAPI, NotFoundError, PlatformClient, type ProcessOpts, type ProcessResult, type Project, type ProjectConfig, type ProjectCost, ProjectsAPI, type RawMetrics, type Region, RegionsAPI, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type ScaleOpts, type Secret, SecretsAPI, type Service, type ServiceMetrics, type ServiceUpdate, ServicesAPI, type SetSecretOpts, StorageAPI, type StreamEvent, TimeoutError, type Transaction, type TransactionPage, Volume, type VolumeInfo, type Workspace, WorkspacesAPI, resolveProjectId };
1801
+ export { type Account, type Addon, type AddonType, AddonsAPI, type ApiKey, type ApiKeyScope, ApiKeysAPI, AuthenticationError, type Balance, BillingAPI, type CLIOptions, type CLIResult, type CodeContext, CodeSandbox, ConfigApplyError, type ConfigResult, ConflictError, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateApiKeyOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type CreateWorkspaceOpts, type CreatedApiKey, type DeployEvent, DeployHandle, type DomainInfo, type DomainStatus, DomainsAPI, Execution, ExecutionError, type FileInfo, type FsOpts, GitHubAPI, type ListTransactionsOpts, Lizard, LizardCLI, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, MetricsAPI, NotFoundError, PlatformClient, type ProcessOpts, type ProcessResult, type Project, type ProjectConfig, type ProjectCost, ProjectsAPI, type RawMetrics, type Region, RegionsAPI, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type SandboxSnapshot, type ScaleOpts, type Secret, SecretsAPI, type Service, type ServiceMetrics, type ServiceUpdate, ServicesAPI, type SetSecretOpts, type SnapshotOpts, type SnapshotWaitOpts, StorageAPI, type StreamEvent, TimeoutError, type Transaction, type TransactionPage, Volume, type VolumeInfo, type Workspace, WorkspacesAPI, resolveProjectId };
package/dist/index.d.ts CHANGED
@@ -650,6 +650,9 @@ interface SandboxInfo {
650
650
  /** Region the sandbox runs in — the volume's region when one is attached. */
651
651
  region?: string;
652
652
  status?: string;
653
+ pauseError?: string | null;
654
+ size?: 'small' | 'medium' | 'large' | null;
655
+ pricePerHour?: number | null;
653
656
  cpus?: number;
654
657
  memoryMb?: number;
655
658
  metadata?: Record<string, string>;
@@ -665,6 +668,8 @@ interface SandboxOpts extends ConnectionOpts {
665
668
  /** Exact project ID — skips resolving {@link SandboxOpts.project}. */
666
669
  projectId?: string;
667
670
  template?: string;
671
+ snapshotId?: string;
672
+ size?: 'small' | 'medium' | 'large';
668
673
  metadata?: Record<string, string>;
669
674
  envs?: Record<string, string>;
670
675
  timeoutMs?: number;
@@ -731,6 +736,27 @@ declare class SandboxClient {
731
736
  }>;
732
737
  }
733
738
 
739
+ interface SandboxSnapshot {
740
+ id: string;
741
+ name: string;
742
+ projectId: string;
743
+ workspaceId: string;
744
+ region: string;
745
+ sourceSandboxId: string;
746
+ status: 'building' | 'warming' | 'ready' | 'paused' | 'suspended' | 'failed';
747
+ poolSize: number;
748
+ readyCount: number;
749
+ error: string | null;
750
+ cpus: number;
751
+ memoryMb: number;
752
+ createdAt: number;
753
+ }
754
+ interface SnapshotOpts {
755
+ poolSize?: number;
756
+ }
757
+ interface SnapshotWaitOpts extends ConnectionOpts {
758
+ waitTimeoutMs?: number;
759
+ }
734
760
  /**
735
761
  * A Linux sandbox running on Kubernetes.
736
762
  *
@@ -856,20 +882,12 @@ declare class Sandbox extends SandboxClient {
856
882
  * @returns `true` if the sandbox was terminated, `false` if it was already gone.
857
883
  */
858
884
  kill(opts?: ConnectionOpts): Promise<boolean>;
859
- /**
860
- * Request sandbox pause. The Kubernetes backend returns HTTP 501.
861
- *
862
- * @deprecated Unsupported on Kubernetes. Save files to a {@link Volume}
863
- * and attach it to a new sandbox instead.
864
- */
885
+ /** Queue a CRIU checkpoint and stop compute. Use waitForStatus('paused') before resuming. */
865
886
  pause(opts?: ConnectionOpts): Promise<boolean>;
866
- /**
867
- * Request sandbox resume. The Kubernetes backend returns HTTP 501.
868
- *
869
- * @deprecated Unsupported on Kubernetes. Use {@link Sandbox.connect} to
870
- * reconnect to a running sandbox.
871
- */
887
+ /** Queue restoration of saved memory and files. Use waitForStatus('running') before executing. */
872
888
  resume(opts?: ConnectionOpts): Promise<boolean>;
889
+ /** Wait for an asynchronous pause/resume operation to finish. */
890
+ waitForStatus(status: 'paused' | 'running', opts?: SnapshotWaitOpts): Promise<SandboxInfo>;
873
891
  /**
874
892
  * Get metadata and status information about this sandbox.
875
893
  */
@@ -899,18 +917,25 @@ declare class Sandbox extends SandboxClient {
899
917
  count?: number;
900
918
  timeoutMs?: number;
901
919
  }): Promise<unknown>;
902
- /** Snapshot creation is unsupported on Kubernetes; the backend returns HTTP 501. */
903
- snapshot(name?: string): Promise<unknown>;
920
+ /** Capture memory and workspace files. Disconnect active clients before capturing. */
921
+ snapshot(name?: string, opts?: SnapshotOpts): Promise<SandboxSnapshot>;
904
922
  unexpose(port: number): Promise<void>;
905
923
  logs(opts?: {
906
924
  tail?: number;
907
925
  signal?: AbortSignal;
908
926
  }): AsyncGenerator<StreamEvent, any, any>;
909
- static snapshots(projectId: string, opts?: ConnectionOpts): Promise<unknown[]>;
910
- /** Snapshot restore is unsupported on Kubernetes; the backend returns HTTP 501. */
927
+ static snapshots(projectId: string, opts?: ConnectionOpts): Promise<SandboxSnapshot[]>;
928
+ static getSnapshot(snapshotId: string, opts?: ConnectionOpts): Promise<SandboxSnapshot>;
929
+ /** Start a sandbox from an available warm copy in the snapshot's project and region. */
911
930
  static restore(snapshotId: string, opts?: ConnectionOpts & {
912
931
  timeoutMs?: number;
913
932
  }): Promise<Sandbox>;
933
+ static setSnapshotWarmPool(snapshotId: string, poolSize: number, opts?: ConnectionOpts): Promise<SandboxSnapshot>;
934
+ /** Release idle warm copies; retain saved state and existing claimed sandboxes. */
935
+ static pauseSnapshot(snapshotId: string, opts?: ConnectionOpts): Promise<SandboxSnapshot>;
936
+ /** Refill the configured warm pool without another capture. */
937
+ static resumeSnapshot(snapshotId: string, opts?: ConnectionOpts): Promise<SandboxSnapshot>;
938
+ static waitForSnapshot(snapshotId: string, opts?: SnapshotWaitOpts): Promise<SandboxSnapshot>;
914
939
  static deleteSnapshot(snapshotId: string, opts?: ConnectionOpts): Promise<void>;
915
940
  private resolveOpts;
916
941
  }
@@ -1773,4 +1798,4 @@ declare class LizardCLI {
1773
1798
  }): Promise<CLIResult>;
1774
1799
  }
1775
1800
 
1776
- export { type Account, type Addon, type AddonType, AddonsAPI, type ApiKey, type ApiKeyScope, ApiKeysAPI, AuthenticationError, type Balance, BillingAPI, type CLIOptions, type CLIResult, type CodeContext, CodeSandbox, ConfigApplyError, type ConfigResult, ConflictError, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateApiKeyOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type CreateWorkspaceOpts, type CreatedApiKey, type DeployEvent, DeployHandle, type DomainInfo, type DomainStatus, DomainsAPI, Execution, ExecutionError, type FileInfo, type FsOpts, GitHubAPI, type ListTransactionsOpts, Lizard, LizardCLI, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, MetricsAPI, NotFoundError, PlatformClient, type ProcessOpts, type ProcessResult, type Project, type ProjectConfig, type ProjectCost, ProjectsAPI, type RawMetrics, type Region, RegionsAPI, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type ScaleOpts, type Secret, SecretsAPI, type Service, type ServiceMetrics, type ServiceUpdate, ServicesAPI, type SetSecretOpts, StorageAPI, type StreamEvent, TimeoutError, type Transaction, type TransactionPage, Volume, type VolumeInfo, type Workspace, WorkspacesAPI, resolveProjectId };
1801
+ export { type Account, type Addon, type AddonType, AddonsAPI, type ApiKey, type ApiKeyScope, ApiKeysAPI, AuthenticationError, type Balance, BillingAPI, type CLIOptions, type CLIResult, type CodeContext, CodeSandbox, ConfigApplyError, type ConfigResult, ConflictError, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateApiKeyOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type CreateWorkspaceOpts, type CreatedApiKey, type DeployEvent, DeployHandle, type DomainInfo, type DomainStatus, DomainsAPI, Execution, ExecutionError, type FileInfo, type FsOpts, GitHubAPI, type ListTransactionsOpts, Lizard, LizardCLI, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, MetricsAPI, NotFoundError, PlatformClient, type ProcessOpts, type ProcessResult, type Project, type ProjectConfig, type ProjectCost, ProjectsAPI, type RawMetrics, type Region, RegionsAPI, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type SandboxSnapshot, type ScaleOpts, type Secret, SecretsAPI, type Service, type ServiceMetrics, type ServiceUpdate, ServicesAPI, type SetSecretOpts, type SnapshotOpts, type SnapshotWaitOpts, StorageAPI, type StreamEvent, TimeoutError, type Transaction, type TransactionPage, Volume, type VolumeInfo, type Workspace, WorkspacesAPI, resolveProjectId };
package/dist/index.js CHANGED
@@ -849,6 +849,8 @@ var SandboxClient = class {
849
849
  headers: config.headers,
850
850
  body: JSON.stringify({
851
851
  template,
852
+ snapshotId: opts?.snapshotId,
853
+ size: opts?.size,
852
854
  timeoutMs,
853
855
  projectId,
854
856
  metadata: opts?.metadata,
@@ -981,24 +983,28 @@ var Sandbox = class extends SandboxClient {
981
983
  async kill(opts) {
982
984
  return SandboxClient.killSandbox(this.sandboxId, this.resolveOpts(opts));
983
985
  }
984
- /**
985
- * Request sandbox pause. The Kubernetes backend returns HTTP 501.
986
- *
987
- * @deprecated Unsupported on Kubernetes. Save files to a {@link Volume}
988
- * and attach it to a new sandbox instead.
989
- */
986
+ /** Queue a CRIU checkpoint and stop compute. Use waitForStatus('paused') before resuming. */
990
987
  async pause(opts) {
991
988
  return SandboxClient.pauseSandbox(this.sandboxId, this.resolveOpts(opts));
992
989
  }
993
- /**
994
- * Request sandbox resume. The Kubernetes backend returns HTTP 501.
995
- *
996
- * @deprecated Unsupported on Kubernetes. Use {@link Sandbox.connect} to
997
- * reconnect to a running sandbox.
998
- */
990
+ /** Queue restoration of saved memory and files. Use waitForStatus('running') before executing. */
999
991
  async resume(opts) {
1000
992
  return SandboxClient.resumeSandbox(this.sandboxId, this.resolveOpts(opts));
1001
993
  }
994
+ /** Wait for an asynchronous pause/resume operation to finish. */
995
+ async waitForStatus(status, opts) {
996
+ const deadline = Date.now() + (opts?.waitTimeoutMs ?? 6e5);
997
+ do {
998
+ const info = await this.getInfo(opts);
999
+ if (info.status === status) return info;
1000
+ if (["failed", "stopped", "deleted"].includes(info.status ?? "") || info.pauseError) {
1001
+ throw new ConflictError(info.pauseError ?? `Sandbox is ${info.status}`);
1002
+ }
1003
+ if (Date.now() >= deadline) break;
1004
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
1005
+ } while (Date.now() < deadline);
1006
+ throw new TimeoutError(`Sandbox did not become ${status} within the wait timeout`);
1007
+ }
1002
1008
  /**
1003
1009
  * Get metadata and status information about this sandbox.
1004
1010
  */
@@ -1034,9 +1040,9 @@ var Sandbox = class extends SandboxClient {
1034
1040
  fork(opts = {}) {
1035
1041
  return new PlatformClient(this.resolveOpts()).post(`/api/sandboxes/${this.sandboxId}/fork`, { count: opts.count ?? 1, timeoutMs: opts.timeoutMs ?? 0 });
1036
1042
  }
1037
- /** Snapshot creation is unsupported on Kubernetes; the backend returns HTTP 501. */
1038
- snapshot(name) {
1039
- return new PlatformClient(this.resolveOpts()).post(`/api/sandboxes/${this.sandboxId}/snapshot`, { name });
1043
+ /** Capture memory and workspace files. Disconnect active clients before capturing. */
1044
+ snapshot(name = `Snapshot ${this.sandboxId}`, opts = {}) {
1045
+ return new PlatformClient(this.resolveOpts()).post(`/api/sandboxes/${this.sandboxId}/snapshot`, { name, poolSize: opts.poolSize ?? 5 });
1040
1046
  }
1041
1047
  unexpose(port) {
1042
1048
  return new PlatformClient(this.resolveOpts()).delete(`/api/sandboxes/${this.sandboxId}/expose/${port}`);
@@ -1045,13 +1051,45 @@ var Sandbox = class extends SandboxClient {
1045
1051
  return new PlatformClient(this.resolveOpts()).events(query(`/api/sandboxes/${this.sandboxId}/logs`, { tail: opts.tail }), { signal: opts.signal });
1046
1052
  }
1047
1053
  static snapshots(projectId, opts) {
1048
- return new PlatformClient(opts ?? {}).get(`/api/projects/${projectId}/snapshots`);
1054
+ return new PlatformClient(opts ?? {}).get(`/api/projects/${encodeURIComponent(projectId)}/snapshots`);
1055
+ }
1056
+ static getSnapshot(snapshotId, opts) {
1057
+ return new PlatformClient(opts ?? {}).get(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}`);
1049
1058
  }
1050
- /** Snapshot restore is unsupported on Kubernetes; the backend returns HTTP 501. */
1059
+ /** Start a sandbox from an available warm copy in the snapshot's project and region. */
1051
1060
  static async restore(snapshotId, opts) {
1052
- const result = await new PlatformClient(opts ?? {}).post(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}/fork`, { timeoutMs: opts?.timeoutMs ?? 0 });
1061
+ const client = new PlatformClient(opts ?? {});
1062
+ const snapshot = await this.getSnapshot(snapshotId, opts);
1063
+ const result = await client.post("/api/sandboxes", {
1064
+ snapshotId,
1065
+ projectId: snapshot.projectId,
1066
+ region: snapshot.region,
1067
+ timeoutMs: opts?.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS
1068
+ });
1053
1069
  return new this({ ...opts, sandboxId: result.sandboxId ?? result.id });
1054
1070
  }
1071
+ static setSnapshotWarmPool(snapshotId, poolSize, opts) {
1072
+ return new PlatformClient(opts ?? {}).patch(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}`, { poolSize });
1073
+ }
1074
+ /** Release idle warm copies; retain saved state and existing claimed sandboxes. */
1075
+ static pauseSnapshot(snapshotId, opts) {
1076
+ return new PlatformClient(opts ?? {}).post(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}/pause`, {});
1077
+ }
1078
+ /** Refill the configured warm pool without another capture. */
1079
+ static resumeSnapshot(snapshotId, opts) {
1080
+ return new PlatformClient(opts ?? {}).post(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}/resume`, {});
1081
+ }
1082
+ static async waitForSnapshot(snapshotId, opts) {
1083
+ const deadline = Date.now() + (opts?.waitTimeoutMs ?? 9e5);
1084
+ do {
1085
+ const snapshot = await this.getSnapshot(snapshotId, opts);
1086
+ if (snapshot.status === "ready" && snapshot.readyCount >= Math.max(1, snapshot.poolSize ?? 1)) return snapshot;
1087
+ if (["failed", "paused", "suspended"].includes(snapshot.status)) throw new ConflictError(snapshot.error ?? `Snapshot is ${snapshot.status}`);
1088
+ if (Date.now() >= deadline) break;
1089
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
1090
+ } while (Date.now() < deadline);
1091
+ throw new TimeoutError("Snapshot did not become ready within the wait timeout");
1092
+ }
1055
1093
  static deleteSnapshot(snapshotId, opts) {
1056
1094
  return new PlatformClient(opts ?? {}).delete(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}`);
1057
1095
  }
package/dist/index.mjs CHANGED
@@ -798,6 +798,8 @@ var SandboxClient = class {
798
798
  headers: config.headers,
799
799
  body: JSON.stringify({
800
800
  template,
801
+ snapshotId: opts?.snapshotId,
802
+ size: opts?.size,
801
803
  timeoutMs,
802
804
  projectId,
803
805
  metadata: opts?.metadata,
@@ -930,24 +932,28 @@ var Sandbox = class extends SandboxClient {
930
932
  async kill(opts) {
931
933
  return SandboxClient.killSandbox(this.sandboxId, this.resolveOpts(opts));
932
934
  }
933
- /**
934
- * Request sandbox pause. The Kubernetes backend returns HTTP 501.
935
- *
936
- * @deprecated Unsupported on Kubernetes. Save files to a {@link Volume}
937
- * and attach it to a new sandbox instead.
938
- */
935
+ /** Queue a CRIU checkpoint and stop compute. Use waitForStatus('paused') before resuming. */
939
936
  async pause(opts) {
940
937
  return SandboxClient.pauseSandbox(this.sandboxId, this.resolveOpts(opts));
941
938
  }
942
- /**
943
- * Request sandbox resume. The Kubernetes backend returns HTTP 501.
944
- *
945
- * @deprecated Unsupported on Kubernetes. Use {@link Sandbox.connect} to
946
- * reconnect to a running sandbox.
947
- */
939
+ /** Queue restoration of saved memory and files. Use waitForStatus('running') before executing. */
948
940
  async resume(opts) {
949
941
  return SandboxClient.resumeSandbox(this.sandboxId, this.resolveOpts(opts));
950
942
  }
943
+ /** Wait for an asynchronous pause/resume operation to finish. */
944
+ async waitForStatus(status, opts) {
945
+ const deadline = Date.now() + (opts?.waitTimeoutMs ?? 6e5);
946
+ do {
947
+ const info = await this.getInfo(opts);
948
+ if (info.status === status) return info;
949
+ if (["failed", "stopped", "deleted"].includes(info.status ?? "") || info.pauseError) {
950
+ throw new ConflictError(info.pauseError ?? `Sandbox is ${info.status}`);
951
+ }
952
+ if (Date.now() >= deadline) break;
953
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
954
+ } while (Date.now() < deadline);
955
+ throw new TimeoutError(`Sandbox did not become ${status} within the wait timeout`);
956
+ }
951
957
  /**
952
958
  * Get metadata and status information about this sandbox.
953
959
  */
@@ -983,9 +989,9 @@ var Sandbox = class extends SandboxClient {
983
989
  fork(opts = {}) {
984
990
  return new PlatformClient(this.resolveOpts()).post(`/api/sandboxes/${this.sandboxId}/fork`, { count: opts.count ?? 1, timeoutMs: opts.timeoutMs ?? 0 });
985
991
  }
986
- /** Snapshot creation is unsupported on Kubernetes; the backend returns HTTP 501. */
987
- snapshot(name) {
988
- return new PlatformClient(this.resolveOpts()).post(`/api/sandboxes/${this.sandboxId}/snapshot`, { name });
992
+ /** Capture memory and workspace files. Disconnect active clients before capturing. */
993
+ snapshot(name = `Snapshot ${this.sandboxId}`, opts = {}) {
994
+ return new PlatformClient(this.resolveOpts()).post(`/api/sandboxes/${this.sandboxId}/snapshot`, { name, poolSize: opts.poolSize ?? 5 });
989
995
  }
990
996
  unexpose(port) {
991
997
  return new PlatformClient(this.resolveOpts()).delete(`/api/sandboxes/${this.sandboxId}/expose/${port}`);
@@ -994,13 +1000,45 @@ var Sandbox = class extends SandboxClient {
994
1000
  return new PlatformClient(this.resolveOpts()).events(query(`/api/sandboxes/${this.sandboxId}/logs`, { tail: opts.tail }), { signal: opts.signal });
995
1001
  }
996
1002
  static snapshots(projectId, opts) {
997
- return new PlatformClient(opts ?? {}).get(`/api/projects/${projectId}/snapshots`);
1003
+ return new PlatformClient(opts ?? {}).get(`/api/projects/${encodeURIComponent(projectId)}/snapshots`);
1004
+ }
1005
+ static getSnapshot(snapshotId, opts) {
1006
+ return new PlatformClient(opts ?? {}).get(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}`);
998
1007
  }
999
- /** Snapshot restore is unsupported on Kubernetes; the backend returns HTTP 501. */
1008
+ /** Start a sandbox from an available warm copy in the snapshot's project and region. */
1000
1009
  static async restore(snapshotId, opts) {
1001
- const result = await new PlatformClient(opts ?? {}).post(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}/fork`, { timeoutMs: opts?.timeoutMs ?? 0 });
1010
+ const client = new PlatformClient(opts ?? {});
1011
+ const snapshot = await this.getSnapshot(snapshotId, opts);
1012
+ const result = await client.post("/api/sandboxes", {
1013
+ snapshotId,
1014
+ projectId: snapshot.projectId,
1015
+ region: snapshot.region,
1016
+ timeoutMs: opts?.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS
1017
+ });
1002
1018
  return new this({ ...opts, sandboxId: result.sandboxId ?? result.id });
1003
1019
  }
1020
+ static setSnapshotWarmPool(snapshotId, poolSize, opts) {
1021
+ return new PlatformClient(opts ?? {}).patch(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}`, { poolSize });
1022
+ }
1023
+ /** Release idle warm copies; retain saved state and existing claimed sandboxes. */
1024
+ static pauseSnapshot(snapshotId, opts) {
1025
+ return new PlatformClient(opts ?? {}).post(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}/pause`, {});
1026
+ }
1027
+ /** Refill the configured warm pool without another capture. */
1028
+ static resumeSnapshot(snapshotId, opts) {
1029
+ return new PlatformClient(opts ?? {}).post(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}/resume`, {});
1030
+ }
1031
+ static async waitForSnapshot(snapshotId, opts) {
1032
+ const deadline = Date.now() + (opts?.waitTimeoutMs ?? 9e5);
1033
+ do {
1034
+ const snapshot = await this.getSnapshot(snapshotId, opts);
1035
+ if (snapshot.status === "ready" && snapshot.readyCount >= Math.max(1, snapshot.poolSize ?? 1)) return snapshot;
1036
+ if (["failed", "paused", "suspended"].includes(snapshot.status)) throw new ConflictError(snapshot.error ?? `Snapshot is ${snapshot.status}`);
1037
+ if (Date.now() >= deadline) break;
1038
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
1039
+ } while (Date.now() < deadline);
1040
+ throw new TimeoutError("Snapshot did not become ready within the wait timeout");
1041
+ }
1004
1042
  static deleteSnapshot(snapshotId, opts) {
1005
1043
  return new PlatformClient(opts ?? {}).delete(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}`);
1006
1044
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lizard-build/sdk",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
4
4
  "description": "TypeScript SDK for Lizard: Kubernetes sandboxes, code execution and cloud apps.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",