tensorlake 0.5.34 → 0.5.36

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.cts CHANGED
@@ -97,6 +97,11 @@ interface CreateSandboxOptions {
97
97
  snapshotId?: string;
98
98
  /** Optional name for the sandbox. Named sandboxes support suspend/resume. When absent the sandbox is ephemeral. */
99
99
  name?: string;
100
+ /**
101
+ * Local cloud-init file path or HTTP(S) URL for the sandbox. Supported for
102
+ * fresh sandboxes and filesystem-only snapshot restores.
103
+ */
104
+ cloudInit?: string | URL;
100
105
  }
101
106
  interface UpdateSandboxOptions {
102
107
  /** New name for the sandbox. Naming an ephemeral sandbox enables suspend/resume. */
package/dist/index.d.ts CHANGED
@@ -97,6 +97,11 @@ interface CreateSandboxOptions {
97
97
  snapshotId?: string;
98
98
  /** Optional name for the sandbox. Named sandboxes support suspend/resume. When absent the sandbox is ephemeral. */
99
99
  name?: string;
100
+ /**
101
+ * Local cloud-init file path or HTTP(S) URL for the sandbox. Supported for
102
+ * fresh sandboxes and filesystem-only snapshot restores.
103
+ */
104
+ cloudInit?: string | URL;
100
105
  }
101
106
  interface UpdateSandboxOptions {
102
107
  /** New name for the sandbox. Naming an ephemeral sandbox enables suspend/resume. */
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TI
19
19
  var init_defaults = __esm({
20
20
  "src/defaults.ts"() {
21
21
  "use strict";
22
- SDK_VERSION = "0.5.34";
22
+ SDK_VERSION = "0.5.36";
23
23
  API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
24
24
  API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
25
25
  NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
@@ -139,6 +139,56 @@ var init_errors = __esm({
139
139
  }
140
140
  });
141
141
 
142
+ // src/sdk-timings.ts
143
+ function sdkTimingsEnabled() {
144
+ const value = process.env[SDK_TIMINGS_ENV_VAR];
145
+ return value === "1" || value === "true" || value === "yes";
146
+ }
147
+ function sdkTimingPayloadsEnabled() {
148
+ const value = process.env[SDK_TIMING_PAYLOADS_ENV_VAR];
149
+ return value === "1" || value === "true" || value === "yes";
150
+ }
151
+ function nowMs() {
152
+ return performance.now();
153
+ }
154
+ function logSdkTiming(op, phase, startedAt, fields = {}) {
155
+ if (!sdkTimingsEnabled()) return;
156
+ logSdkTimingEvent(op, phase, {
157
+ ...fields,
158
+ duration_ms: Math.round((nowMs() - startedAt) * 100) / 100
159
+ });
160
+ }
161
+ function logSdkTimingEvent(op, phase, fields = {}) {
162
+ if (!sdkTimingsEnabled()) return;
163
+ const parts = [
164
+ "[tensorlake:sdk-timing]",
165
+ `op=${op}`,
166
+ `phase=${phase}`
167
+ ];
168
+ for (const [key, value] of Object.entries(fields)) {
169
+ if (value == null) continue;
170
+ parts.push(`${key}=${formatSdkTimingValue(value)}`);
171
+ }
172
+ console.error(parts.join(" "));
173
+ }
174
+ function formatSdkTimingValue(value) {
175
+ if (typeof value === "number" || typeof value === "boolean") {
176
+ return String(value);
177
+ }
178
+ if (/^[A-Za-z0-9_./:@-]+$/.test(value)) {
179
+ return value;
180
+ }
181
+ return JSON.stringify(value);
182
+ }
183
+ var SDK_TIMINGS_ENV_VAR, SDK_TIMING_PAYLOADS_ENV_VAR;
184
+ var init_sdk_timings = __esm({
185
+ "src/sdk-timings.ts"() {
186
+ "use strict";
187
+ SDK_TIMINGS_ENV_VAR = "TENSORLAKE_SDK_TIMINGS";
188
+ SDK_TIMING_PAYLOADS_ENV_VAR = "TENSORLAKE_SDK_TIMING_PAYLOADS";
189
+ }
190
+ });
191
+
142
192
  // src/http.ts
143
193
  import {
144
194
  Agent,
@@ -164,6 +214,10 @@ function normalizeRequestBody(body) {
164
214
  }
165
215
  return body;
166
216
  }
217
+ function stripPathPayload(path2) {
218
+ const queryStart = path2.search(/[?#]/);
219
+ return queryStart === -1 ? path2 : path2.slice(0, queryStart);
220
+ }
167
221
  function throwMappedError(status, body, path2) {
168
222
  let message = body;
169
223
  try {
@@ -213,6 +267,7 @@ var init_http = __esm({
213
267
  "use strict";
214
268
  init_defaults();
215
269
  init_errors();
270
+ init_sdk_timings();
216
271
  REQUEST_TIMEOUT_HEADER = "X-Tensorlake-Request-Timeout-Ms";
217
272
  setGlobalDispatcher(
218
273
  new Agent({
@@ -339,9 +394,18 @@ var init_http = __esm({
339
394
  const { traceparent, traceId } = _HttpClient.makeTraceparent();
340
395
  headers["traceparent"] = traceparent;
341
396
  let lastError;
397
+ const operationStart = nowMs();
398
+ const timingPath = sdkTimingPayloadsEnabled() ? path2 : stripPathPayload(path2);
342
399
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
343
400
  if (attempt > 0) {
344
401
  const delay = this.retryBackoffMs * Math.pow(2, attempt - 1);
402
+ logSdkTimingEvent("http.request", "retry_sleep_start", {
403
+ method,
404
+ path: timingPath,
405
+ attempt,
406
+ delay_ms: delay,
407
+ server_trace_id: traceId
408
+ });
345
409
  await sleep(delay);
346
410
  }
347
411
  this.abortController = new AbortController();
@@ -351,6 +415,7 @@ var init_http = __esm({
351
415
  this.abortController?.abort();
352
416
  }, this.timeoutMs);
353
417
  const combinedSignal = signal ? anySignal([signal, this.abortController.signal]) : this.abortController.signal;
418
+ const attemptStart = nowMs();
354
419
  try {
355
420
  const response = await undiciFetch(url, {
356
421
  method,
@@ -359,15 +424,45 @@ var init_http = __esm({
359
424
  signal: combinedSignal
360
425
  });
361
426
  if (timeoutId != null) clearTimeout(timeoutId);
362
- if (response.ok) return { response, traceId };
427
+ logSdkTiming("http.request", "headers", attemptStart, {
428
+ method,
429
+ path: timingPath,
430
+ status: response.status,
431
+ attempt,
432
+ server_trace_id: traceId
433
+ });
434
+ if (response.ok) {
435
+ logSdkTiming("http.request", "complete", operationStart, {
436
+ method,
437
+ path: timingPath,
438
+ status: response.status,
439
+ attempts: attempt + 1,
440
+ server_trace_id: traceId
441
+ });
442
+ return { response, traceId };
443
+ }
363
444
  if (retryableStatusCodes.has(response.status) && attempt < this.maxRetries) {
364
445
  lastError = new RemoteAPIError(
365
446
  response.status,
366
447
  await response.text().catch(() => "")
367
448
  );
449
+ logSdkTimingEvent("http.request", "retryable_status", {
450
+ method,
451
+ path: timingPath,
452
+ status: response.status,
453
+ attempt,
454
+ server_trace_id: traceId
455
+ });
368
456
  continue;
369
457
  }
370
458
  if (allowHttpErrors || allowedErrorStatusCodes.has(response.status)) {
459
+ logSdkTiming("http.request", "complete", operationStart, {
460
+ method,
461
+ path: timingPath,
462
+ status: response.status,
463
+ attempts: attempt + 1,
464
+ server_trace_id: traceId
465
+ });
371
466
  return { response, traceId };
372
467
  }
373
468
  const errorBody = await response.text().catch(() => "");
@@ -383,6 +478,13 @@ var init_http = __esm({
383
478
  });
384
479
  }
385
480
  lastError = err instanceof Error ? err : new Error(String(err));
481
+ logSdkTiming("http.request", "error", attemptStart, {
482
+ method,
483
+ path: timingPath,
484
+ attempt,
485
+ server_trace_id: traceId,
486
+ error: timedOut ? `request timed out after ${this.timeoutMs}ms` : describeError(err)
487
+ });
386
488
  if (attempt >= this.maxRetries) {
387
489
  const detail = timedOut ? `request timed out after ${this.timeoutMs}ms` : describeError(err);
388
490
  throw new SandboxConnectionError(detail, { cause: err });
@@ -3097,6 +3199,7 @@ var init_sandbox = __esm({
3097
3199
  init_models();
3098
3200
  init_tunnel();
3099
3201
  init_sse();
3202
+ init_sdk_timings();
3100
3203
  init_url();
3101
3204
  DEFAULT_PROCESS_USER = "tl-user";
3102
3205
  SandboxProxyConnection = class {
@@ -3124,7 +3227,12 @@ var init_sandbox = __esm({
3124
3227
  if (this.resolvePromise != null) {
3125
3228
  return this.resolvePromise;
3126
3229
  }
3127
- this.resolvePromise = this.resolveProxyInfo(this.sandbox._getLifecycleIdentifier()).then((info) => {
3230
+ const identifier = this.sandbox._getLifecycleIdentifier();
3231
+ const resolveStart = nowMs();
3232
+ logSdkTimingEvent("sandbox.proxy", "resolve_start", {
3233
+ sandbox_id: identifier
3234
+ });
3235
+ this.resolvePromise = this.resolveProxyInfo(identifier).then((info) => {
3128
3236
  this.resolveProxyInfo = void 0;
3129
3237
  this.sandbox.traceId = info.traceId;
3130
3238
  this.sandbox._setLifecycleIdentifier(info.sandboxId);
@@ -3134,6 +3242,12 @@ var init_sandbox = __esm({
3134
3242
  const nextHttp = this.configureProxy(proxyUrl, info.sandboxId, this.routingHint);
3135
3243
  this.http.close();
3136
3244
  this.http = nextHttp;
3245
+ logSdkTiming("sandbox.proxy", "resolve_complete", resolveStart, {
3246
+ sandbox_id: info.sandboxId,
3247
+ server_trace_id: info.traceId,
3248
+ routing_hint: this.routingHint,
3249
+ ingress_endpoint: info.ingressEndpoint
3250
+ });
3137
3251
  }).finally(() => {
3138
3252
  this.resolvePromise = null;
3139
3253
  });
@@ -3594,6 +3708,7 @@ var init_sandbox = __esm({
3594
3708
  * the process, streams output, and delivers the exit code over one connection.
3595
3709
  */
3596
3710
  async run(command, options) {
3711
+ const opStart = nowMs();
3597
3712
  const body = { command };
3598
3713
  if (options?.args) body.args = options.args;
3599
3714
  if (options?.env) body.env = options.env;
@@ -3601,15 +3716,28 @@ var init_sandbox = __esm({
3601
3716
  if (options?.timeout != null) body.timeout = options.timeout;
3602
3717
  const user = processUserPayload(options?.user);
3603
3718
  body.user = user;
3719
+ logSdkTimingEvent("sandbox.run", "start", {
3720
+ sandbox_id: this.sandboxId,
3721
+ command: sdkTimingPayloadsEnabled() ? command : void 0,
3722
+ command_length: command.length
3723
+ });
3724
+ const requestStart = nowMs();
3604
3725
  const sseStream = await this.http.requestStream(
3605
3726
  "POST",
3606
3727
  "/api/v1/processes/run",
3607
3728
  { json: body }
3608
3729
  );
3609
3730
  const traceId = sseStream.traceId;
3731
+ logSdkTiming("sandbox.run", "stream_headers", requestStart, {
3732
+ sandbox_id: this.sandboxId,
3733
+ server_trace_id: traceId,
3734
+ command: sdkTimingPayloadsEnabled() ? command : void 0,
3735
+ command_length: command.length
3736
+ });
3610
3737
  const stdoutLines = [];
3611
3738
  const stderrLines = [];
3612
3739
  let exitCode = -1;
3740
+ const streamStart = nowMs();
3613
3741
  for await (const raw of parseSSEStream(sseStream)) {
3614
3742
  if (typeof raw.line === "string") {
3615
3743
  if (raw.stream === "stderr") {
@@ -3625,6 +3753,20 @@ var init_sandbox = __esm({
3625
3753
  }
3626
3754
  }
3627
3755
  }
3756
+ logSdkTiming("sandbox.run", "stream_complete", streamStart, {
3757
+ sandbox_id: this.sandboxId,
3758
+ server_trace_id: traceId,
3759
+ command: sdkTimingPayloadsEnabled() ? command : void 0,
3760
+ command_length: command.length,
3761
+ exit_code: exitCode
3762
+ });
3763
+ logSdkTiming("sandbox.run", "complete", opStart, {
3764
+ sandbox_id: this.sandboxId,
3765
+ server_trace_id: traceId,
3766
+ command: sdkTimingPayloadsEnabled() ? command : void 0,
3767
+ command_length: command.length,
3768
+ exit_code: exitCode
3769
+ });
3628
3770
  return Object.assign(
3629
3771
  { exitCode, stdout: stdoutLines.join("\n"), stderr: stderrLines.join("\n") },
3630
3772
  { traceId }
@@ -3972,6 +4114,50 @@ var client_exports = {};
3972
4114
  __export(client_exports, {
3973
4115
  SandboxClient: () => SandboxClient
3974
4116
  });
4117
+ import { readFile } from "fs/promises";
4118
+ function cloudInitIncludeData(source) {
4119
+ const isWindowsPath = /^[A-Za-z]:[\\/]/.test(source);
4120
+ try {
4121
+ const url = new URL(source);
4122
+ if ((url.protocol === "http:" || url.protocol === "https:") && url.hostname.length > 0) {
4123
+ return Buffer.from(`#include
4124
+ ${url.toString()}
4125
+ `, "utf8");
4126
+ }
4127
+ if (!isWindowsPath) {
4128
+ throw new SandboxError(
4129
+ "cloud-init URL must be an absolute HTTP(S) URL with a host"
4130
+ );
4131
+ }
4132
+ } catch (error) {
4133
+ if (error instanceof SandboxError) throw error;
4134
+ if (!isWindowsPath && /^[A-Za-z][A-Za-z0-9+.-]*:/.test(source)) {
4135
+ throw new SandboxError(
4136
+ "cloud-init URL must be an absolute HTTP(S) URL with a host"
4137
+ );
4138
+ }
4139
+ }
4140
+ return void 0;
4141
+ }
4142
+ async function readCloudInitBase64(cloudInit) {
4143
+ if (cloudInit == null) return void 0;
4144
+ const sourceText = cloudInit instanceof URL ? cloudInit.toString() : cloudInit;
4145
+ const includeData = cloudInitIncludeData(sourceText);
4146
+ const data = includeData ?? await readFile(sourceText).catch((error) => {
4147
+ throw new SandboxError(
4148
+ `failed to read cloud-init file ${JSON.stringify(sourceText)}: ${error}`
4149
+ );
4150
+ });
4151
+ if (data.length === 0) {
4152
+ throw new SandboxError("cloud-init user data must not be empty");
4153
+ }
4154
+ if (data.length > MAX_CLOUD_INIT_USER_DATA_BYTES) {
4155
+ throw new SandboxError(
4156
+ `cloud-init user data exceeds ${MAX_CLOUD_INIT_USER_DATA_BYTES} byte limit`
4157
+ );
4158
+ }
4159
+ return data.toString("base64");
4160
+ }
3975
4161
  function resolveRequestTimeoutMs(options) {
3976
4162
  if (options?.requestTimeout != null) {
3977
4163
  return secondsToMillis4(options.requestTimeout);
@@ -4049,7 +4235,7 @@ function validateUserPort(port) {
4049
4235
  function dedupeAndSortPorts(ports) {
4050
4236
  return [...new Set(ports)].sort((a, b) => a - b);
4051
4237
  }
4052
- var CREATE_SANDBOX_RETRYABLE_STATUS_CODES, CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES, COPY_SANDBOX_ALLOWED_ERROR_STATUS_CODES, SandboxClient, RESERVED_SANDBOX_MANAGEMENT_PORT;
4238
+ var CREATE_SANDBOX_RETRYABLE_STATUS_CODES, CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES, COPY_SANDBOX_ALLOWED_ERROR_STATUS_CODES, MAX_CLOUD_INIT_USER_DATA_BYTES, SandboxClient, RESERVED_SANDBOX_MANAGEMENT_PORT;
4053
4239
  var init_client = __esm({
4054
4240
  "src/client.ts"() {
4055
4241
  "use strict";
@@ -4058,10 +4244,12 @@ var init_client = __esm({
4058
4244
  init_http();
4059
4245
  init_models();
4060
4246
  init_sandbox();
4247
+ init_sdk_timings();
4061
4248
  init_url();
4062
4249
  CREATE_SANDBOX_RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([502, 503]);
4063
4250
  CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES = /* @__PURE__ */ new Set([504]);
4064
4251
  COPY_SANDBOX_ALLOWED_ERROR_STATUS_CODES = /* @__PURE__ */ new Set([422, 504]);
4252
+ MAX_CLOUD_INIT_USER_DATA_BYTES = 16 * 1024;
4065
4253
  SandboxClient = class _SandboxClient {
4066
4254
  http;
4067
4255
  apiUrl;
@@ -4152,6 +4340,7 @@ var init_client = __esm({
4152
4340
  // --- Sandbox CRUD ---
4153
4341
  /** Create a new sandbox. Returns immediately; the sandbox may still be starting. Use `createAndConnect()` for a blocking, ready-to-use handle. */
4154
4342
  async create(options) {
4343
+ const cloudInitBase64 = await readCloudInitBase64(options?.cloudInit);
4155
4344
  const body = {
4156
4345
  resources: {
4157
4346
  cpus: options?.cpus ?? 1,
@@ -4164,6 +4353,7 @@ var init_client = __esm({
4164
4353
  if (options?.entrypoint != null) body.entrypoint = options.entrypoint;
4165
4354
  if (options?.snapshotId != null) body.snapshot_id = options.snapshotId;
4166
4355
  if (options?.name != null) body.name = options.name;
4356
+ if (cloudInitBase64 != null) body.cloud_init_base64 = cloudInitBase64;
4167
4357
  if (options?.allowInternetAccess === false || options?.allowOut != null || options?.denyOut != null) {
4168
4358
  body.network = {
4169
4359
  allow_internet_access: options?.allowInternetAccess ?? true,
@@ -4581,9 +4771,24 @@ var init_client = __esm({
4581
4771
  * @throws {SandboxError} If the sandbox terminates during startup or the timeout elapses.
4582
4772
  */
4583
4773
  async createAndConnect(options) {
4774
+ if (options?.poolId != null && options?.cloudInit != null) {
4775
+ throw new SandboxError("cloud-init cannot be used with poolId");
4776
+ }
4777
+ const opStart = nowMs();
4584
4778
  const requestTimeout = options?.requestTimeout ?? options?.startupTimeout ?? this.requestTimeoutMs / 1e3;
4585
4779
  const requestClient = this.withRequestTimeout(requestTimeout);
4780
+ logSdkTimingEvent("sandbox.create", "start", {
4781
+ request_timeout_s: requestTimeout,
4782
+ image: options?.image,
4783
+ pool_id: options?.poolId
4784
+ });
4785
+ const createStart = nowMs();
4586
4786
  const result = options?.poolId != null ? await requestClient.claim(options.poolId) : await requestClient.create(options);
4787
+ logSdkTiming("sandbox.create", options?.poolId != null ? "claim_response" : "create_response", createStart, {
4788
+ sandbox_id: result.sandboxId,
4789
+ status: result.status,
4790
+ server_trace_id: result.traceId
4791
+ });
4587
4792
  const requestedName = options?.poolId != null ? null : options?.name ?? null;
4588
4793
  const finishConnect = (routingHint, name, ingressEndpoint) => {
4589
4794
  const sandbox = requestClient.connect(
@@ -4596,6 +4801,13 @@ var init_client = __esm({
4596
4801
  sandbox.traceId = result.traceId;
4597
4802
  sandbox._setLifecycleIdentifier(result.sandboxId);
4598
4803
  sandbox._setName(name ?? requestedName);
4804
+ logSdkTiming("sandbox.create", "complete", opStart, {
4805
+ sandbox_id: result.sandboxId,
4806
+ status: "running" /* RUNNING */,
4807
+ server_trace_id: result.traceId,
4808
+ routing_hint: routingHint,
4809
+ ingress_endpoint: ingressEndpoint
4810
+ });
4599
4811
  return sandbox;
4600
4812
  };
4601
4813
  if (result.status === "running" /* RUNNING */) {
@@ -4620,7 +4832,13 @@ var init_client = __esm({
4620
4832
  }
4621
4833
  const deadline = Date.now() + secondsToMillis4(requestTimeout);
4622
4834
  while (Date.now() < deadline) {
4835
+ const pollStart = nowMs();
4623
4836
  const info = await requestClient.get(result.sandboxId);
4837
+ logSdkTiming("sandbox.create", "poll_response", pollStart, {
4838
+ sandbox_id: result.sandboxId,
4839
+ status: info.status,
4840
+ server_trace_id: info.traceId
4841
+ });
4624
4842
  if (info.status === "running" /* RUNNING */) {
4625
4843
  return finishConnect(info.routingHint, info.name, info.ingressEndpoint);
4626
4844
  }