@lizard-build/sdk 0.1.29 → 0.1.30

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/index.d.mts CHANGED
@@ -150,7 +150,14 @@ interface SandboxOpts extends ConnectionOpts {
150
150
  metadata?: Record<string, string>;
151
151
  envs?: Record<string, string>;
152
152
  timeoutMs?: number;
153
+ /** Attach a persistent volume by id, mounted at `/data`. */
153
154
  volumeId?: string;
155
+ /**
156
+ * Attach a persistent volume by name, mounted at `/data`. A volume's name is its
157
+ * key inside a project, so this is usually what you want — see {@link Volume}.
158
+ * Requires the sandbox's project to be given as an exact `projectId`.
159
+ */
160
+ volumeName?: string;
154
161
  }
155
162
  /**
156
163
  * Low-level HTTP client for the Lizard sandbox API.
@@ -293,9 +300,16 @@ declare class Sandbox extends SandboxClient {
293
300
  * nothing is written to disk, so a paused sandbox does not survive a host
294
301
  * failure. Resume with `sandbox.resume()` or `Sandbox.connect(id)`.
295
302
  *
296
- * Pausing does not stop the timeout: a paused sandbox is still deleted at
297
- * its original expiry (default 5 minutes). Pass `timeoutMs: 0` at create
298
- * time to opt out of expiry.
303
+ * Pausing does not buy you time past the original deadline. Resuming pushes the
304
+ * expiry forward by however long the sandbox was paused — so a pause/resume cycle
305
+ * costs no runtime — but that adjustment only happens on `resume()`. A sandbox left
306
+ * paused past its original `expiresAt` is deleted there, within a minute, and
307
+ * `resume()` then returns `false`.
308
+ *
309
+ * In other words: pause and come back *before* the original deadline and you lose
310
+ * nothing; leave it paused across the deadline and it is gone. Pass `timeoutMs: 0`
311
+ * at create time to opt out of expiry entirely, which is the only way to park a
312
+ * sandbox indefinitely.
299
313
  *
300
314
  * @returns `true` if paused successfully.
301
315
  */
@@ -303,7 +317,12 @@ declare class Sandbox extends SandboxClient {
303
317
  /**
304
318
  * Resume a paused sandbox by unfreezing its vCPUs.
305
319
  *
306
- * @returns `true` if resumed successfully.
320
+ * The expiry is pushed forward by the time spent paused, so the timeout measures
321
+ * running time rather than wall-clock — provided the original deadline had not
322
+ * already passed while paused. See {@link pause}.
323
+ *
324
+ * @returns `true` if resumed successfully, `false` if the sandbox no longer exists
325
+ * (including because it expired while paused).
307
326
  */
308
327
  resume(opts?: ConnectionOpts): Promise<boolean>;
309
328
  /**
@@ -780,6 +799,14 @@ declare class AuthenticationError extends LizardError {
780
799
  declare class NotFoundError extends LizardError {
781
800
  constructor(message?: string);
782
801
  }
802
+ /**
803
+ * The resource already exists — most often a volume whose name is already taken in
804
+ * the project. Volume names are the key inside a project, so `Volume.create` refuses
805
+ * to make a second one; catch this, or call `Volume.getOrCreate` instead.
806
+ */
807
+ declare class ConflictError extends LizardError {
808
+ constructor(message?: string);
809
+ }
783
810
  declare class TimeoutError extends LizardError {
784
811
  constructor(message?: string);
785
812
  }
@@ -950,19 +977,48 @@ interface CreateVolumeOpts extends ConnectionOpts {
950
977
  }
951
978
  /**
952
979
  * A persistent volume that outlives sandboxes.
953
- * Mount it to a sandbox via `Sandbox.create({ volumeId: volume.volumeId })`.
980
+ *
981
+ * A volume's **name** is its key inside a project: names are unique per project, and
982
+ * every method here accepts either a name or the generated id wherever a volume is
983
+ * addressed. Prefer the name — it is the thing you chose and can reconstruct, while
984
+ * the id only exists after the first create.
985
+ *
986
+ * Names are slugs: lowercase letters, digits and dashes, starting and ending with a
987
+ * letter or digit, up to 64 characters.
988
+ *
989
+ * Mount one to a sandbox via `Sandbox.create({ volumeName: 'my-data' })`.
954
990
  */
955
991
  declare class Volume {
992
+ /** The volume's generated id. Stable, but you rarely need it — address by name. */
956
993
  readonly volumeId: string;
994
+ /** The volume's name: unique within its project, and usable anywhere `volumeId` is. */
995
+ readonly name?: string;
957
996
  private readonly config;
958
997
  constructor(opts: {
959
998
  volumeId: string;
999
+ name?: string;
960
1000
  } & ConnectionOpts);
1001
+ /**
1002
+ * Create a volume. Throws `ConflictError` (409) if the project already has one with
1003
+ * this name — use {@link getOrCreate} when you want "make sure this exists" instead.
1004
+ */
961
1005
  static create(projectId: string, name: string, opts?: CreateVolumeOpts): Promise<Volume>;
962
- static get(projectId: string, volumeId: string, opts?: ConnectionOpts): Promise<Volume>;
1006
+ /**
1007
+ * Return the project's volume with this name, creating it first if it does not
1008
+ * exist yet. This is the reason a volume's name is its key: an agent that wants
1009
+ * "the scratch disk for this task" no longer has to store an id between runs.
1010
+ *
1011
+ * An existing volume is returned as-is — `sizeGb` applies only to a fresh create
1012
+ * and never resizes one that is already there.
1013
+ */
1014
+ static getOrCreate(projectId: string, name: string, opts?: CreateVolumeOpts): Promise<Volume>;
1015
+ /** Look a volume up by name or by id. */
1016
+ static get(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise<Volume>;
963
1017
  static list(projectId: string, opts?: ConnectionOpts): Promise<VolumeInfo[]>;
1018
+ /** Delete a volume by name or by id, without constructing one first. */
1019
+ static delete(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise<void>;
964
1020
  getInfo(projectId: string): Promise<VolumeInfo>;
965
1021
  delete(projectId: string): Promise<void>;
966
1022
  }
967
1023
 
968
- export { type Addon, type AddonType, AuthenticationError, type CodeContext, CodeSandbox, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type DeployEvent, DeployHandle, type DomainInfo, Execution, ExecutionError, type FileInfo, type FsOpts, Lizard, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, NotFoundError, type ProcessOpts, type ProcessResult, type Project, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type ScaleOpts, type Secret, type Service, type ServiceMetrics, type SetSecretOpts, TimeoutError, Volume, type VolumeInfo, resolveProjectId };
1024
+ export { type Addon, type AddonType, AuthenticationError, type CodeContext, CodeSandbox, ConflictError, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type DeployEvent, DeployHandle, type DomainInfo, Execution, ExecutionError, type FileInfo, type FsOpts, Lizard, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, NotFoundError, type ProcessOpts, type ProcessResult, type Project, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type ScaleOpts, type Secret, type Service, type ServiceMetrics, type SetSecretOpts, TimeoutError, Volume, type VolumeInfo, resolveProjectId };
package/dist/index.d.ts CHANGED
@@ -150,7 +150,14 @@ interface SandboxOpts extends ConnectionOpts {
150
150
  metadata?: Record<string, string>;
151
151
  envs?: Record<string, string>;
152
152
  timeoutMs?: number;
153
+ /** Attach a persistent volume by id, mounted at `/data`. */
153
154
  volumeId?: string;
155
+ /**
156
+ * Attach a persistent volume by name, mounted at `/data`. A volume's name is its
157
+ * key inside a project, so this is usually what you want — see {@link Volume}.
158
+ * Requires the sandbox's project to be given as an exact `projectId`.
159
+ */
160
+ volumeName?: string;
154
161
  }
155
162
  /**
156
163
  * Low-level HTTP client for the Lizard sandbox API.
@@ -293,9 +300,16 @@ declare class Sandbox extends SandboxClient {
293
300
  * nothing is written to disk, so a paused sandbox does not survive a host
294
301
  * failure. Resume with `sandbox.resume()` or `Sandbox.connect(id)`.
295
302
  *
296
- * Pausing does not stop the timeout: a paused sandbox is still deleted at
297
- * its original expiry (default 5 minutes). Pass `timeoutMs: 0` at create
298
- * time to opt out of expiry.
303
+ * Pausing does not buy you time past the original deadline. Resuming pushes the
304
+ * expiry forward by however long the sandbox was paused — so a pause/resume cycle
305
+ * costs no runtime — but that adjustment only happens on `resume()`. A sandbox left
306
+ * paused past its original `expiresAt` is deleted there, within a minute, and
307
+ * `resume()` then returns `false`.
308
+ *
309
+ * In other words: pause and come back *before* the original deadline and you lose
310
+ * nothing; leave it paused across the deadline and it is gone. Pass `timeoutMs: 0`
311
+ * at create time to opt out of expiry entirely, which is the only way to park a
312
+ * sandbox indefinitely.
299
313
  *
300
314
  * @returns `true` if paused successfully.
301
315
  */
@@ -303,7 +317,12 @@ declare class Sandbox extends SandboxClient {
303
317
  /**
304
318
  * Resume a paused sandbox by unfreezing its vCPUs.
305
319
  *
306
- * @returns `true` if resumed successfully.
320
+ * The expiry is pushed forward by the time spent paused, so the timeout measures
321
+ * running time rather than wall-clock — provided the original deadline had not
322
+ * already passed while paused. See {@link pause}.
323
+ *
324
+ * @returns `true` if resumed successfully, `false` if the sandbox no longer exists
325
+ * (including because it expired while paused).
307
326
  */
308
327
  resume(opts?: ConnectionOpts): Promise<boolean>;
309
328
  /**
@@ -780,6 +799,14 @@ declare class AuthenticationError extends LizardError {
780
799
  declare class NotFoundError extends LizardError {
781
800
  constructor(message?: string);
782
801
  }
802
+ /**
803
+ * The resource already exists — most often a volume whose name is already taken in
804
+ * the project. Volume names are the key inside a project, so `Volume.create` refuses
805
+ * to make a second one; catch this, or call `Volume.getOrCreate` instead.
806
+ */
807
+ declare class ConflictError extends LizardError {
808
+ constructor(message?: string);
809
+ }
783
810
  declare class TimeoutError extends LizardError {
784
811
  constructor(message?: string);
785
812
  }
@@ -950,19 +977,48 @@ interface CreateVolumeOpts extends ConnectionOpts {
950
977
  }
951
978
  /**
952
979
  * A persistent volume that outlives sandboxes.
953
- * Mount it to a sandbox via `Sandbox.create({ volumeId: volume.volumeId })`.
980
+ *
981
+ * A volume's **name** is its key inside a project: names are unique per project, and
982
+ * every method here accepts either a name or the generated id wherever a volume is
983
+ * addressed. Prefer the name — it is the thing you chose and can reconstruct, while
984
+ * the id only exists after the first create.
985
+ *
986
+ * Names are slugs: lowercase letters, digits and dashes, starting and ending with a
987
+ * letter or digit, up to 64 characters.
988
+ *
989
+ * Mount one to a sandbox via `Sandbox.create({ volumeName: 'my-data' })`.
954
990
  */
955
991
  declare class Volume {
992
+ /** The volume's generated id. Stable, but you rarely need it — address by name. */
956
993
  readonly volumeId: string;
994
+ /** The volume's name: unique within its project, and usable anywhere `volumeId` is. */
995
+ readonly name?: string;
957
996
  private readonly config;
958
997
  constructor(opts: {
959
998
  volumeId: string;
999
+ name?: string;
960
1000
  } & ConnectionOpts);
1001
+ /**
1002
+ * Create a volume. Throws `ConflictError` (409) if the project already has one with
1003
+ * this name — use {@link getOrCreate} when you want "make sure this exists" instead.
1004
+ */
961
1005
  static create(projectId: string, name: string, opts?: CreateVolumeOpts): Promise<Volume>;
962
- static get(projectId: string, volumeId: string, opts?: ConnectionOpts): Promise<Volume>;
1006
+ /**
1007
+ * Return the project's volume with this name, creating it first if it does not
1008
+ * exist yet. This is the reason a volume's name is its key: an agent that wants
1009
+ * "the scratch disk for this task" no longer has to store an id between runs.
1010
+ *
1011
+ * An existing volume is returned as-is — `sizeGb` applies only to a fresh create
1012
+ * and never resizes one that is already there.
1013
+ */
1014
+ static getOrCreate(projectId: string, name: string, opts?: CreateVolumeOpts): Promise<Volume>;
1015
+ /** Look a volume up by name or by id. */
1016
+ static get(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise<Volume>;
963
1017
  static list(projectId: string, opts?: ConnectionOpts): Promise<VolumeInfo[]>;
1018
+ /** Delete a volume by name or by id, without constructing one first. */
1019
+ static delete(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise<void>;
964
1020
  getInfo(projectId: string): Promise<VolumeInfo>;
965
1021
  delete(projectId: string): Promise<void>;
966
1022
  }
967
1023
 
968
- export { type Addon, type AddonType, AuthenticationError, type CodeContext, CodeSandbox, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type DeployEvent, DeployHandle, type DomainInfo, Execution, ExecutionError, type FileInfo, type FsOpts, Lizard, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, NotFoundError, type ProcessOpts, type ProcessResult, type Project, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type ScaleOpts, type Secret, type Service, type ServiceMetrics, type SetSecretOpts, TimeoutError, Volume, type VolumeInfo, resolveProjectId };
1024
+ export { type Addon, type AddonType, AuthenticationError, type CodeContext, CodeSandbox, ConflictError, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type DeployEvent, DeployHandle, type DomainInfo, Execution, ExecutionError, type FileInfo, type FsOpts, Lizard, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, NotFoundError, type ProcessOpts, type ProcessResult, type Project, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type ScaleOpts, type Secret, type Service, type ServiceMetrics, type SetSecretOpts, TimeoutError, Volume, type VolumeInfo, resolveProjectId };
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AuthenticationError: () => AuthenticationError,
24
24
  CodeSandbox: () => CodeSandbox,
25
+ ConflictError: () => ConflictError,
25
26
  DeployHandle: () => DeployHandle,
26
27
  Lizard: () => Lizard,
27
28
  LizardError: () => LizardError,
@@ -74,6 +75,12 @@ var NotFoundError = class extends LizardError {
74
75
  this.name = "NotFoundError";
75
76
  }
76
77
  };
78
+ var ConflictError = class extends LizardError {
79
+ constructor(message = "Resource already exists") {
80
+ super(message);
81
+ this.name = "ConflictError";
82
+ }
83
+ };
77
84
  var TimeoutError = class extends LizardError {
78
85
  constructor(message = "Sandbox operation timed out") {
79
86
  super(message);
@@ -90,6 +97,7 @@ async function handleApiError(res) {
90
97
  }
91
98
  if (res.status === 401 || res.status === 403) throw new AuthenticationError(message);
92
99
  if (res.status === 404) throw new NotFoundError(message);
100
+ if (res.status === 409) throw new ConflictError(message);
93
101
  if (res.status === 408 || res.status === 504) throw new TimeoutError(message);
94
102
  throw new LizardError(`API error ${res.status}: ${message}`);
95
103
  }
@@ -278,7 +286,8 @@ var SandboxClient = class {
278
286
  projectId,
279
287
  metadata: opts?.metadata,
280
288
  envs: opts?.envs,
281
- volumeId: opts?.volumeId
289
+ volumeId: opts?.volumeId,
290
+ volumeName: opts?.volumeName
282
291
  })
283
292
  });
284
293
  if (!res.ok) await handleApiError(res);
@@ -407,9 +416,16 @@ var Sandbox = class extends SandboxClient {
407
416
  * nothing is written to disk, so a paused sandbox does not survive a host
408
417
  * failure. Resume with `sandbox.resume()` or `Sandbox.connect(id)`.
409
418
  *
410
- * Pausing does not stop the timeout: a paused sandbox is still deleted at
411
- * its original expiry (default 5 minutes). Pass `timeoutMs: 0` at create
412
- * time to opt out of expiry.
419
+ * Pausing does not buy you time past the original deadline. Resuming pushes the
420
+ * expiry forward by however long the sandbox was paused — so a pause/resume cycle
421
+ * costs no runtime — but that adjustment only happens on `resume()`. A sandbox left
422
+ * paused past its original `expiresAt` is deleted there, within a minute, and
423
+ * `resume()` then returns `false`.
424
+ *
425
+ * In other words: pause and come back *before* the original deadline and you lose
426
+ * nothing; leave it paused across the deadline and it is gone. Pass `timeoutMs: 0`
427
+ * at create time to opt out of expiry entirely, which is the only way to park a
428
+ * sandbox indefinitely.
413
429
  *
414
430
  * @returns `true` if paused successfully.
415
431
  */
@@ -419,7 +435,12 @@ var Sandbox = class extends SandboxClient {
419
435
  /**
420
436
  * Resume a paused sandbox by unfreezing its vCPUs.
421
437
  *
422
- * @returns `true` if resumed successfully.
438
+ * The expiry is pushed forward by the time spent paused, so the timeout measures
439
+ * running time rather than wall-clock — provided the original deadline had not
440
+ * already passed while paused. See {@link pause}.
441
+ *
442
+ * @returns `true` if resumed successfully, `false` if the sandbox no longer exists
443
+ * (including because it expired while paused).
423
444
  */
424
445
  async resume(opts) {
425
446
  return SandboxClient.resumeSandbox(this.sandboxId, this.resolveOpts(opts));
@@ -1111,8 +1132,13 @@ async function* readLines(body) {
1111
1132
  var Volume = class _Volume {
1112
1133
  constructor(opts) {
1113
1134
  this.volumeId = opts.volumeId;
1135
+ this.name = opts.name;
1114
1136
  this.config = new ConnectionConfig(opts);
1115
1137
  }
1138
+ /**
1139
+ * Create a volume. Throws `ConflictError` (409) if the project already has one with
1140
+ * this name — use {@link getOrCreate} when you want "make sure this exists" instead.
1141
+ */
1116
1142
  static async create(projectId, name, opts) {
1117
1143
  const config = new ConnectionConfig(opts);
1118
1144
  const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
@@ -1122,15 +1148,36 @@ var Volume = class _Volume {
1122
1148
  });
1123
1149
  if (!res.ok) await handleApiError(res);
1124
1150
  const vol = await res.json();
1125
- return new _Volume({ volumeId: vol.id, ...opts });
1151
+ return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1126
1152
  }
1127
- static async get(projectId, volumeId, opts) {
1153
+ /**
1154
+ * Return the project's volume with this name, creating it first if it does not
1155
+ * exist yet. This is the reason a volume's name is its key: an agent that wants
1156
+ * "the scratch disk for this task" no longer has to store an id between runs.
1157
+ *
1158
+ * An existing volume is returned as-is — `sizeGb` applies only to a fresh create
1159
+ * and never resizes one that is already there.
1160
+ */
1161
+ static async getOrCreate(projectId, name, opts) {
1128
1162
  const config = new ConnectionConfig(opts);
1129
- const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${volumeId}`, {
1163
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
1164
+ method: "POST",
1165
+ headers: config.headers,
1166
+ body: JSON.stringify({ name, sizeGb: opts?.sizeGb ?? 5, getOrCreate: true })
1167
+ });
1168
+ if (!res.ok) await handleApiError(res);
1169
+ const vol = await res.json();
1170
+ return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1171
+ }
1172
+ /** Look a volume up by name or by id. */
1173
+ static async get(projectId, nameOrId, opts) {
1174
+ const config = new ConnectionConfig(opts);
1175
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
1130
1176
  headers: config.headers
1131
1177
  });
1132
1178
  if (!res.ok) await handleApiError(res);
1133
- return new _Volume({ volumeId, ...opts });
1179
+ const vol = await res.json();
1180
+ return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1134
1181
  }
1135
1182
  static async list(projectId, opts) {
1136
1183
  const config = new ConnectionConfig(opts);
@@ -1140,6 +1187,15 @@ var Volume = class _Volume {
1140
1187
  if (!res.ok) await handleApiError(res);
1141
1188
  return res.json();
1142
1189
  }
1190
+ /** Delete a volume by name or by id, without constructing one first. */
1191
+ static async delete(projectId, nameOrId, opts) {
1192
+ const config = new ConnectionConfig(opts);
1193
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
1194
+ method: "DELETE",
1195
+ headers: config.headers
1196
+ });
1197
+ if (!res.ok) await handleApiError(res);
1198
+ }
1143
1199
  async getInfo(projectId) {
1144
1200
  const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
1145
1201
  headers: this.config.headers
@@ -1159,6 +1215,7 @@ var Volume = class _Volume {
1159
1215
  0 && (module.exports = {
1160
1216
  AuthenticationError,
1161
1217
  CodeSandbox,
1218
+ ConflictError,
1162
1219
  DeployHandle,
1163
1220
  Lizard,
1164
1221
  LizardError,
package/dist/index.mjs CHANGED
@@ -39,6 +39,12 @@ var NotFoundError = class extends LizardError {
39
39
  this.name = "NotFoundError";
40
40
  }
41
41
  };
42
+ var ConflictError = class extends LizardError {
43
+ constructor(message = "Resource already exists") {
44
+ super(message);
45
+ this.name = "ConflictError";
46
+ }
47
+ };
42
48
  var TimeoutError = class extends LizardError {
43
49
  constructor(message = "Sandbox operation timed out") {
44
50
  super(message);
@@ -55,6 +61,7 @@ async function handleApiError(res) {
55
61
  }
56
62
  if (res.status === 401 || res.status === 403) throw new AuthenticationError(message);
57
63
  if (res.status === 404) throw new NotFoundError(message);
64
+ if (res.status === 409) throw new ConflictError(message);
58
65
  if (res.status === 408 || res.status === 504) throw new TimeoutError(message);
59
66
  throw new LizardError(`API error ${res.status}: ${message}`);
60
67
  }
@@ -243,7 +250,8 @@ var SandboxClient = class {
243
250
  projectId,
244
251
  metadata: opts?.metadata,
245
252
  envs: opts?.envs,
246
- volumeId: opts?.volumeId
253
+ volumeId: opts?.volumeId,
254
+ volumeName: opts?.volumeName
247
255
  })
248
256
  });
249
257
  if (!res.ok) await handleApiError(res);
@@ -372,9 +380,16 @@ var Sandbox = class extends SandboxClient {
372
380
  * nothing is written to disk, so a paused sandbox does not survive a host
373
381
  * failure. Resume with `sandbox.resume()` or `Sandbox.connect(id)`.
374
382
  *
375
- * Pausing does not stop the timeout: a paused sandbox is still deleted at
376
- * its original expiry (default 5 minutes). Pass `timeoutMs: 0` at create
377
- * time to opt out of expiry.
383
+ * Pausing does not buy you time past the original deadline. Resuming pushes the
384
+ * expiry forward by however long the sandbox was paused — so a pause/resume cycle
385
+ * costs no runtime — but that adjustment only happens on `resume()`. A sandbox left
386
+ * paused past its original `expiresAt` is deleted there, within a minute, and
387
+ * `resume()` then returns `false`.
388
+ *
389
+ * In other words: pause and come back *before* the original deadline and you lose
390
+ * nothing; leave it paused across the deadline and it is gone. Pass `timeoutMs: 0`
391
+ * at create time to opt out of expiry entirely, which is the only way to park a
392
+ * sandbox indefinitely.
378
393
  *
379
394
  * @returns `true` if paused successfully.
380
395
  */
@@ -384,7 +399,12 @@ var Sandbox = class extends SandboxClient {
384
399
  /**
385
400
  * Resume a paused sandbox by unfreezing its vCPUs.
386
401
  *
387
- * @returns `true` if resumed successfully.
402
+ * The expiry is pushed forward by the time spent paused, so the timeout measures
403
+ * running time rather than wall-clock — provided the original deadline had not
404
+ * already passed while paused. See {@link pause}.
405
+ *
406
+ * @returns `true` if resumed successfully, `false` if the sandbox no longer exists
407
+ * (including because it expired while paused).
388
408
  */
389
409
  async resume(opts) {
390
410
  return SandboxClient.resumeSandbox(this.sandboxId, this.resolveOpts(opts));
@@ -1076,8 +1096,13 @@ async function* readLines(body) {
1076
1096
  var Volume = class _Volume {
1077
1097
  constructor(opts) {
1078
1098
  this.volumeId = opts.volumeId;
1099
+ this.name = opts.name;
1079
1100
  this.config = new ConnectionConfig(opts);
1080
1101
  }
1102
+ /**
1103
+ * Create a volume. Throws `ConflictError` (409) if the project already has one with
1104
+ * this name — use {@link getOrCreate} when you want "make sure this exists" instead.
1105
+ */
1081
1106
  static async create(projectId, name, opts) {
1082
1107
  const config = new ConnectionConfig(opts);
1083
1108
  const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
@@ -1087,15 +1112,36 @@ var Volume = class _Volume {
1087
1112
  });
1088
1113
  if (!res.ok) await handleApiError(res);
1089
1114
  const vol = await res.json();
1090
- return new _Volume({ volumeId: vol.id, ...opts });
1115
+ return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1091
1116
  }
1092
- static async get(projectId, volumeId, opts) {
1117
+ /**
1118
+ * Return the project's volume with this name, creating it first if it does not
1119
+ * exist yet. This is the reason a volume's name is its key: an agent that wants
1120
+ * "the scratch disk for this task" no longer has to store an id between runs.
1121
+ *
1122
+ * An existing volume is returned as-is — `sizeGb` applies only to a fresh create
1123
+ * and never resizes one that is already there.
1124
+ */
1125
+ static async getOrCreate(projectId, name, opts) {
1093
1126
  const config = new ConnectionConfig(opts);
1094
- const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${volumeId}`, {
1127
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
1128
+ method: "POST",
1129
+ headers: config.headers,
1130
+ body: JSON.stringify({ name, sizeGb: opts?.sizeGb ?? 5, getOrCreate: true })
1131
+ });
1132
+ if (!res.ok) await handleApiError(res);
1133
+ const vol = await res.json();
1134
+ return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1135
+ }
1136
+ /** Look a volume up by name or by id. */
1137
+ static async get(projectId, nameOrId, opts) {
1138
+ const config = new ConnectionConfig(opts);
1139
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
1095
1140
  headers: config.headers
1096
1141
  });
1097
1142
  if (!res.ok) await handleApiError(res);
1098
- return new _Volume({ volumeId, ...opts });
1143
+ const vol = await res.json();
1144
+ return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1099
1145
  }
1100
1146
  static async list(projectId, opts) {
1101
1147
  const config = new ConnectionConfig(opts);
@@ -1105,6 +1151,15 @@ var Volume = class _Volume {
1105
1151
  if (!res.ok) await handleApiError(res);
1106
1152
  return res.json();
1107
1153
  }
1154
+ /** Delete a volume by name or by id, without constructing one first. */
1155
+ static async delete(projectId, nameOrId, opts) {
1156
+ const config = new ConnectionConfig(opts);
1157
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
1158
+ method: "DELETE",
1159
+ headers: config.headers
1160
+ });
1161
+ if (!res.ok) await handleApiError(res);
1162
+ }
1108
1163
  async getInfo(projectId) {
1109
1164
  const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
1110
1165
  headers: this.config.headers
@@ -1123,6 +1178,7 @@ var Volume = class _Volume {
1123
1178
  export {
1124
1179
  AuthenticationError,
1125
1180
  CodeSandbox,
1181
+ ConflictError,
1126
1182
  DeployHandle,
1127
1183
  Lizard,
1128
1184
  LizardError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lizard-build/sdk",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "description": "Firecracker microVM sandboxes for AI agents — boot Linux in milliseconds, run code, write files, expose ports over HTTPS, then pause a session with memory intact and resume it later. TypeScript SDK for Lizard Sandboxes (lizard.build).",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",