tensorlake 0.5.0 → 0.5.2

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.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/dist/index.cjs CHANGED
@@ -3182,8 +3182,11 @@ var init_sandbox = __esm({
3182
3182
  wsHeaders;
3183
3183
  ownsSandbox = false;
3184
3184
  lifecycleClient = null;
3185
+ lifecycleIdentifier;
3186
+ sandboxName = null;
3185
3187
  constructor(options) {
3186
3188
  this.sandboxId = options.sandboxId;
3189
+ this.lifecycleIdentifier = options.sandboxId;
3187
3190
  const proxyUrl = options.proxyUrl ?? SANDBOX_PROXY_URL;
3188
3191
  const { baseUrl, hostHeader } = resolveProxyTarget(proxyUrl, options.sandboxId);
3189
3192
  this.baseUrl = baseUrl;
@@ -3209,6 +3212,17 @@ var init_sandbox = __esm({
3209
3212
  routingHint: options.routingHint
3210
3213
  });
3211
3214
  }
3215
+ get name() {
3216
+ return this.sandboxName;
3217
+ }
3218
+ /** @internal Used by client wiring to keep locally cached name in sync. */
3219
+ _setName(name) {
3220
+ this.sandboxName = name;
3221
+ }
3222
+ /** @internal Used by lifecycle operations to pin to canonical sandbox ID. */
3223
+ _setLifecycleIdentifier(identifier) {
3224
+ this.lifecycleIdentifier = identifier;
3225
+ }
3212
3226
  /** @internal Used by SandboxClient.createAndConnect to set ownership. */
3213
3227
  _setOwner(client) {
3214
3228
  this.ownsSandbox = true;
@@ -3246,9 +3260,15 @@ var init_sandbox = __esm({
3246
3260
  /* _internal */
3247
3261
  true
3248
3262
  );
3249
- await client.get(options.sandboxId);
3250
- const sandbox = client.connect(options.sandboxId, options.proxyUrl, options.routingHint);
3263
+ const info = await client.get(options.sandboxId);
3264
+ const sandbox = client.connect(
3265
+ info.sandboxId,
3266
+ options.proxyUrl,
3267
+ options.routingHint ?? info.routingHint
3268
+ );
3251
3269
  sandbox.lifecycleClient = client;
3270
+ sandbox._setLifecycleIdentifier(info.sandboxId);
3271
+ sandbox._setName(info.name ?? null);
3252
3272
  return sandbox;
3253
3273
  }
3254
3274
  // --- Static snapshot management ---
@@ -3281,6 +3301,32 @@ var init_sandbox = __esm({
3281
3301
  }
3282
3302
  return this.lifecycleClient;
3283
3303
  }
3304
+ /**
3305
+ * Fetch the current sandbox status from the server.
3306
+ *
3307
+ * Always hits the network — the value is not cached locally because the
3308
+ * status changes over the sandbox's lifecycle.
3309
+ */
3310
+ async status() {
3311
+ const client = this.requireLifecycleClient("read_status");
3312
+ const info = await client.get(this.lifecycleIdentifier);
3313
+ this._setLifecycleIdentifier(info.sandboxId);
3314
+ this._setName(info.name ?? null);
3315
+ return info.status;
3316
+ }
3317
+ /**
3318
+ * Update this sandbox's properties (name, exposed ports, proxy auth).
3319
+ *
3320
+ * Naming an ephemeral sandbox makes it non-ephemeral and enables
3321
+ * suspend/resume.
3322
+ */
3323
+ async update(options) {
3324
+ const client = this.requireLifecycleClient("update");
3325
+ const info = await client.update(this.lifecycleIdentifier, options);
3326
+ this._setLifecycleIdentifier(info.sandboxId);
3327
+ this._setName(info.name ?? null);
3328
+ return info;
3329
+ }
3284
3330
  /**
3285
3331
  * Suspend this sandbox.
3286
3332
  *
@@ -3289,7 +3335,7 @@ var init_sandbox = __esm({
3289
3335
  */
3290
3336
  async suspend(options) {
3291
3337
  const client = this.requireLifecycleClient("suspend");
3292
- await client.suspend(this.sandboxId, options);
3338
+ await client.suspend(this.lifecycleIdentifier, options);
3293
3339
  }
3294
3340
  /**
3295
3341
  * Resume this sandbox.
@@ -3299,7 +3345,7 @@ var init_sandbox = __esm({
3299
3345
  */
3300
3346
  async resume(options) {
3301
3347
  const client = this.requireLifecycleClient("resume");
3302
- await client.resume(this.sandboxId, options);
3348
+ await client.resume(this.lifecycleIdentifier, options);
3303
3349
  }
3304
3350
  /**
3305
3351
  * Create a snapshot of this sandbox's filesystem and wait for it to
@@ -3312,10 +3358,10 @@ var init_sandbox = __esm({
3312
3358
  async checkpoint(options) {
3313
3359
  const client = this.requireLifecycleClient("checkpoint");
3314
3360
  if (options?.wait === false) {
3315
- await client.snapshot(this.sandboxId, { contentMode: options.contentMode });
3361
+ await client.snapshot(this.lifecycleIdentifier, { contentMode: options.contentMode });
3316
3362
  return void 0;
3317
3363
  }
3318
- return client.snapshotAndWait(this.sandboxId, {
3364
+ return client.snapshotAndWait(this.lifecycleIdentifier, {
3319
3365
  timeout: options?.timeout,
3320
3366
  pollInterval: options?.pollInterval,
3321
3367
  contentMode: options?.contentMode
@@ -3327,7 +3373,8 @@ var init_sandbox = __esm({
3327
3373
  async listSnapshots() {
3328
3374
  const client = this.requireLifecycleClient("listSnapshots");
3329
3375
  const all = await client.listSnapshots();
3330
- return all.filter((s) => s.sandboxId === this.sandboxId);
3376
+ const filtered = all.filter((s) => s.sandboxId === this.lifecycleIdentifier);
3377
+ return Object.assign(filtered, { traceId: all.traceId });
3331
3378
  }
3332
3379
  /** Close the HTTP client. The sandbox keeps running. */
3333
3380
  close() {
@@ -3340,7 +3387,7 @@ var init_sandbox = __esm({
3340
3387
  this.lifecycleClient = null;
3341
3388
  this.close();
3342
3389
  if (client) {
3343
- await client.delete(this.sandboxId);
3390
+ await client.delete(this.lifecycleIdentifier);
3344
3391
  }
3345
3392
  }
3346
3393
  // --- High-level convenience ---
@@ -3421,7 +3468,8 @@ var init_sandbox = __esm({
3421
3468
  "GET",
3422
3469
  "/api/v1/processes"
3423
3470
  );
3424
- return (raw.processes ?? []).map((p) => fromSnakeKeys(p));
3471
+ const processes = (raw.processes ?? []).map((p) => fromSnakeKeys(p));
3472
+ return Object.assign(processes, { traceId: raw.traceId });
3425
3473
  }
3426
3474
  /** Get current status and metadata for a process by PID. */
3427
3475
  async getProcess(pid) {
@@ -3673,6 +3721,38 @@ __export(client_exports, {
3673
3721
  function sleep2(ms) {
3674
3722
  return new Promise((resolve) => setTimeout(resolve, ms));
3675
3723
  }
3724
+ function formatStartupFailureMessage(sandboxId, status, options) {
3725
+ const prefix = status === "terminated" /* TERMINATED */ ? `Sandbox ${sandboxId} terminated during startup` : `Sandbox ${sandboxId} became ${status} during startup`;
3726
+ const detail = formatErrorDetails(options.errorDetails);
3727
+ if (detail) {
3728
+ return `${prefix}: ${detail}`;
3729
+ }
3730
+ if (options.terminationReason) {
3731
+ return `${prefix}: termination reason: ${options.terminationReason}`;
3732
+ }
3733
+ return prefix;
3734
+ }
3735
+ function formatErrorDetails(errorDetails) {
3736
+ if (errorDetails == null) return void 0;
3737
+ if (typeof errorDetails === "string") {
3738
+ const detail = errorDetails.trim();
3739
+ return detail || void 0;
3740
+ }
3741
+ if (Array.isArray(errorDetails)) {
3742
+ const parts = errorDetails.map((item) => formatErrorDetails(item)).filter((item) => Boolean(item));
3743
+ return parts.length > 0 ? parts.join("; ") : JSON.stringify(errorDetails);
3744
+ }
3745
+ if (typeof errorDetails === "object") {
3746
+ for (const key of ["message", "detail", "error", "reason"]) {
3747
+ const value = errorDetails[key];
3748
+ if (typeof value === "string" && value.trim()) {
3749
+ return value.trim();
3750
+ }
3751
+ }
3752
+ return JSON.stringify(errorDetails);
3753
+ }
3754
+ return String(errorDetails);
3755
+ }
3676
3756
  function normalizeUserPorts(ports) {
3677
3757
  return dedupeAndSortPorts(ports.map(validateUserPort));
3678
3758
  }
@@ -3758,7 +3838,7 @@ var init_client = __esm({
3758
3838
  resources: {
3759
3839
  cpus: options?.cpus ?? 1,
3760
3840
  memory_mb: options?.memoryMb ?? 1024,
3761
- ephemeral_disk_mb: options?.ephemeralDiskMb ?? 1024
3841
+ ...options?.diskMb != null ? { disk_mb: options.diskMb } : {}
3762
3842
  }
3763
3843
  };
3764
3844
  if (options?.image != null) body.image = options.image;
@@ -3796,9 +3876,10 @@ var init_client = __esm({
3796
3876
  "GET",
3797
3877
  this.path("sandboxes")
3798
3878
  );
3799
- return (raw.sandboxes ?? []).map(
3879
+ const sandboxes = (raw.sandboxes ?? []).map(
3800
3880
  (s) => fromSnakeKeys(s, "sandboxId")
3801
3881
  );
3882
+ return Object.assign(sandboxes, { traceId: raw.traceId });
3802
3883
  }
3803
3884
  /** Update sandbox properties such as name, exposed ports, and proxy auth settings. */
3804
3885
  async update(sandboxId, options) {
@@ -3968,9 +4049,10 @@ var init_client = __esm({
3968
4049
  "GET",
3969
4050
  this.path("snapshots")
3970
4051
  );
3971
- return (raw.snapshots ?? []).map(
4052
+ const snapshots = (raw.snapshots ?? []).map(
3972
4053
  (s) => fromSnakeKeys(s, "snapshotId")
3973
4054
  );
4055
+ return Object.assign(snapshots, { traceId: raw.traceId });
3974
4056
  }
3975
4057
  /** Delete a snapshot by ID. */
3976
4058
  async deleteSnapshot(snapshotId) {
@@ -4050,9 +4132,10 @@ var init_client = __esm({
4050
4132
  "GET",
4051
4133
  this.path("sandbox-pools")
4052
4134
  );
4053
- return (raw.pools ?? []).map(
4135
+ const pools = (raw.pools ?? []).map(
4054
4136
  (p) => fromSnakeKeys(p, "poolId")
4055
4137
  );
4138
+ return Object.assign(pools, { traceId: raw.traceId });
4056
4139
  }
4057
4140
  /** Replace the configuration of an existing sandbox pool. */
4058
4141
  async updatePool(poolId, options) {
@@ -4107,30 +4190,39 @@ var init_client = __esm({
4107
4190
  */
4108
4191
  async createAndConnect(options) {
4109
4192
  const startupTimeout = options?.startupTimeout ?? 60;
4110
- let result;
4111
- if (options?.poolId != null) {
4112
- result = await this.claim(options.poolId);
4113
- } else {
4114
- result = await this.create(options);
4115
- }
4116
- if (result.status === "running" /* RUNNING */) {
4117
- const sandbox = this.connect(result.sandboxId, options?.proxyUrl, result.routingHint);
4193
+ const result = options?.poolId != null ? await this.claim(options.poolId) : await this.create(options);
4194
+ const requestedName = options?.poolId != null ? null : options?.name ?? null;
4195
+ const finishConnect = (routingHint, name) => {
4196
+ const sandbox = this.connect(result.sandboxId, options?.proxyUrl, routingHint);
4118
4197
  sandbox._setOwner(this);
4119
4198
  sandbox.traceId = result.traceId;
4199
+ sandbox._setLifecycleIdentifier(result.sandboxId);
4200
+ sandbox._setName(name ?? requestedName);
4120
4201
  return sandbox;
4202
+ };
4203
+ if (result.status === "running" /* RUNNING */) {
4204
+ return finishConnect(result.routingHint, result.name);
4205
+ }
4206
+ if (result.status === "suspended" /* SUSPENDED */ || result.status === "terminated" /* TERMINATED */) {
4207
+ throw new SandboxError(
4208
+ formatStartupFailureMessage(result.sandboxId, result.status, {
4209
+ errorDetails: result.errorDetails,
4210
+ terminationReason: result.terminationReason
4211
+ })
4212
+ );
4121
4213
  }
4122
4214
  const deadline = Date.now() + startupTimeout * 1e3;
4123
4215
  while (Date.now() < deadline) {
4124
4216
  const info = await this.get(result.sandboxId);
4125
4217
  if (info.status === "running" /* RUNNING */) {
4126
- const sandbox = this.connect(result.sandboxId, options?.proxyUrl, info.routingHint);
4127
- sandbox._setOwner(this);
4128
- sandbox.traceId = result.traceId;
4129
- return sandbox;
4218
+ return finishConnect(info.routingHint, info.name);
4130
4219
  }
4131
- if (info.status === "terminated" /* TERMINATED */) {
4220
+ if (info.status === "suspended" /* SUSPENDED */ || info.status === "terminated" /* TERMINATED */) {
4132
4221
  throw new SandboxError(
4133
- `Sandbox ${result.sandboxId} terminated during startup`
4222
+ formatStartupFailureMessage(result.sandboxId, info.status, {
4223
+ errorDetails: info.errorDetails,
4224
+ terminationReason: info.terminationReason
4225
+ })
4134
4226
  );
4135
4227
  }
4136
4228
  await sleep2(500);
@@ -4907,7 +4999,7 @@ async function executeDockerfilePlan(sandbox, plan, emit, sleep3) {
4907
4999
  );
4908
5000
  }
4909
5001
  }
4910
- async function registerImage(context, name, dockerfile, snapshotId, snapshotUri, isPublic) {
5002
+ async function registerImage(context, name, dockerfile, snapshotId, snapshotSandboxId, snapshotUri, snapshotSizeBytes, rootfsDiskBytes, isPublic) {
4911
5003
  if (!context.organizationId || !context.projectId) {
4912
5004
  throw new Error(
4913
5005
  "Organization ID and Project ID are required. Run 'tl login' and 'tl init'."
@@ -4934,8 +5026,11 @@ async function registerImage(context, name, dockerfile, snapshotId, snapshotUri,
4934
5026
  name,
4935
5027
  dockerfile,
4936
5028
  snapshotId,
5029
+ snapshotSandboxId,
4937
5030
  snapshotUri,
4938
- isPublic
5031
+ snapshotSizeBytes,
5032
+ rootfsDiskBytes,
5033
+ public: isPublic
4939
5034
  })
4940
5035
  });
4941
5036
  if (!response.ok) {
@@ -4965,7 +5060,8 @@ async function createSandboxImage(source, options = {}, deps = {}) {
4965
5060
  sandbox = await client.createAndConnect({
4966
5061
  ...plan.baseImage == null ? {} : { image: plan.baseImage },
4967
5062
  cpus: options.cpus ?? 2,
4968
- memoryMb: options.memoryMb ?? 4096
5063
+ memoryMb: options.memoryMb ?? 4096,
5064
+ ...options.diskMb != null ? { diskMb: options.diskMb } : {}
4969
5065
  });
4970
5066
  emit({
4971
5067
  type: "status",
@@ -4978,14 +5074,23 @@ async function createSandboxImage(source, options = {}, deps = {}) {
4978
5074
  });
4979
5075
  emit({
4980
5076
  type: "snapshot_created",
4981
- snapshot_id: snapshot.snapshotId,
4982
- snapshot_uri: snapshot.snapshotUri ?? null
5077
+ snapshot_id: snapshot.snapshotId
4983
5078
  });
4984
5079
  if (!snapshot.snapshotUri) {
4985
5080
  throw new Error(
4986
5081
  `Snapshot ${snapshot.snapshotId} is missing snapshotUri and cannot be registered as a sandbox image.`
4987
5082
  );
4988
5083
  }
5084
+ if (snapshot.sizeBytes == null) {
5085
+ throw new Error(
5086
+ `Snapshot ${snapshot.snapshotId} is missing sizeBytes and cannot be registered as a sandbox image.`
5087
+ );
5088
+ }
5089
+ if (snapshot.rootfsDiskBytes == null) {
5090
+ throw new Error(
5091
+ `Snapshot ${snapshot.snapshotId} is missing rootfsDiskBytes and cannot be registered as a sandbox image.`
5092
+ );
5093
+ }
4989
5094
  emit({
4990
5095
  type: "status",
4991
5096
  message: `Registering image '${plan.registeredName}'...`
@@ -4995,7 +5100,10 @@ async function createSandboxImage(source, options = {}, deps = {}) {
4995
5100
  plan.registeredName,
4996
5101
  plan.dockerfileText,
4997
5102
  snapshot.snapshotId,
5103
+ snapshot.sandboxId,
4998
5104
  snapshot.snapshotUri,
5105
+ snapshot.sizeBytes,
5106
+ snapshot.rootfsDiskBytes,
4999
5107
  options.isPublic ?? false
5000
5108
  );
5001
5109
  emit({
@@ -5023,27 +5131,33 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
5023
5131
  name: { type: "string", short: "n" },
5024
5132
  cpus: { type: "string" },
5025
5133
  memory: { type: "string" },
5134
+ disk: { type: "string" },
5026
5135
  public: { type: "boolean", default: false }
5027
5136
  }
5028
5137
  });
5029
5138
  const dockerfilePath = parsed.positionals[0];
5030
5139
  if (!dockerfilePath) {
5031
- throw new Error("Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--public]");
5140
+ throw new Error("Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--disk GB] [--public]");
5032
5141
  }
5033
5142
  const cpus = parsed.values.cpus != null ? Number(parsed.values.cpus) : void 0;
5034
5143
  const memoryMb = parsed.values.memory != null ? Number(parsed.values.memory) : void 0;
5144
+ const diskGb = parsed.values.disk != null ? Number(parsed.values.disk) : void 0;
5035
5145
  if (cpus != null && !Number.isFinite(cpus)) {
5036
5146
  throw new Error(`Invalid --cpus value: ${parsed.values.cpus}`);
5037
5147
  }
5038
5148
  if (memoryMb != null && !Number.isInteger(memoryMb)) {
5039
5149
  throw new Error(`Invalid --memory value: ${parsed.values.memory}`);
5040
5150
  }
5151
+ if (diskGb != null && !Number.isInteger(diskGb)) {
5152
+ throw new Error(`Invalid --disk value: ${parsed.values.disk}`);
5153
+ }
5041
5154
  await createSandboxImage(
5042
5155
  dockerfilePath,
5043
5156
  {
5044
5157
  registeredName: parsed.values.name,
5045
5158
  cpus,
5046
5159
  memoryMb,
5160
+ diskMb: diskGb != null ? diskGb * 1024 : void 0,
5047
5161
  isPublic: parsed.values.public
5048
5162
  },
5049
5163
  { emit: ndjsonStdoutEmit }