tensorlake 0.5.17 → 0.5.19

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.js CHANGED
@@ -1,5 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
3
9
  var __esm = (fn, res) => function __init() {
4
10
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
11
  };
@@ -13,7 +19,7 @@ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TI
13
19
  var init_defaults = __esm({
14
20
  "src/defaults.ts"() {
15
21
  "use strict";
16
- SDK_VERSION = "0.5.17";
22
+ SDK_VERSION = "0.5.19";
17
23
  API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
18
24
  API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
19
25
  NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
@@ -4485,11 +4491,6 @@ var init_image = __esm({
4485
4491
  * and registers the snapshot as a named sandbox template.
4486
4492
  */
4487
4493
  async build(options = {}) {
4488
- if ((options.registeredName ?? this._name) === DEFAULT_IMAGE_NAME) {
4489
- console.warn(
4490
- `Building sandbox image with the default name "${DEFAULT_IMAGE_NAME}". Pass \`registeredName\` or \`Image({ name })\` to avoid collisions with other default-named images in this project.`
4491
- );
4492
- }
4493
4494
  const { createSandboxImage: createSandboxImage2 } = await Promise.resolve().then(() => (init_sandbox_image(), sandbox_image_exports));
4494
4495
  return createSandboxImage2(this, options);
4495
4496
  }
@@ -4504,267 +4505,65 @@ var init_image = __esm({
4504
4505
  // src/sandbox-image.ts
4505
4506
  var sandbox_image_exports = {};
4506
4507
  __export(sandbox_image_exports, {
4508
+ __setNativeBindingForTest: () => __setNativeBindingForTest,
4507
4509
  createSandboxImage: () => createSandboxImage,
4508
- defaultRegisteredName: () => defaultRegisteredName,
4509
- loadDockerfilePlan: () => loadDockerfilePlan,
4510
- loadImagePlan: () => loadImagePlan,
4511
- logicalDockerfileLines: () => logicalDockerfileLines,
4512
- registerImage: () => registerImage,
4513
4510
  runCreateSandboxImageCli: () => runCreateSandboxImageCli
4514
4511
  });
4515
- import { readFile, readdir, stat } from "fs/promises";
4516
- import { homedir } from "os";
4512
+ import { createRequire } from "module";
4517
4513
  import path from "path";
4514
+ import { existsSync } from "fs";
4518
4515
  import { parseArgs } from "util";
4519
- function defaultRegisteredName(dockerfilePath) {
4520
- const parsed = path.parse(dockerfilePath);
4521
- if (parsed.name.toLowerCase() === "dockerfile") {
4522
- const parentName = path.basename(path.dirname(dockerfilePath)).trim();
4523
- return parentName || "sandbox-image";
4516
+ function resolveRequire() {
4517
+ if (typeof module !== "undefined" && module.exports != null && typeof __require !== "undefined") {
4518
+ return __require;
4524
4519
  }
4525
- return parsed.name || "sandbox-image";
4520
+ return createRequire(import.meta.url);
4526
4521
  }
4527
- function logicalDockerfileLines(dockerfileText) {
4528
- const logicalLines = [];
4529
- let parts = [];
4530
- let startLine = null;
4531
- for (const [index, rawLine] of dockerfileText.split(/\r?\n/).entries()) {
4532
- const lineNumber = index + 1;
4533
- const stripped = rawLine.trim();
4534
- if (parts.length === 0 && (!stripped || stripped.startsWith("#"))) {
4535
- continue;
4536
- }
4537
- if (startLine == null) {
4538
- startLine = lineNumber;
4539
- }
4540
- let line = rawLine.replace(/\s+$/, "");
4541
- const continued = line.endsWith("\\");
4542
- if (continued) {
4543
- line = line.slice(0, -1);
4544
- }
4545
- const normalized = line.trim();
4546
- if (normalized && !normalized.startsWith("#")) {
4547
- parts.push(normalized);
4548
- }
4549
- if (continued) {
4550
- continue;
4551
- }
4552
- if (parts.length > 0) {
4553
- logicalLines.push({
4554
- lineNumber: startLine ?? lineNumber,
4555
- line: parts.join(" ")
4556
- });
4557
- }
4558
- parts = [];
4559
- startLine = null;
4560
- }
4561
- if (parts.length > 0) {
4562
- logicalLines.push({
4563
- lineNumber: startLine ?? 1,
4564
- line: parts.join(" ")
4565
- });
4566
- }
4567
- return logicalLines;
4568
- }
4569
- function splitInstruction(line, lineNumber) {
4570
- const trimmed = line.trim();
4571
- if (!trimmed) {
4572
- throw new Error(`line ${lineNumber}: empty Dockerfile instruction`);
4573
- }
4574
- const match = trimmed.match(/^(\S+)(?:\s+(.*))?$/);
4575
- if (!match) {
4576
- throw new Error(`line ${lineNumber}: invalid Dockerfile instruction`);
4577
- }
4578
- return {
4579
- keyword: match[1].toUpperCase(),
4580
- value: (match[2] ?? "").trim()
4581
- };
4582
- }
4583
- function shellSplit(input) {
4584
- const tokens = [];
4585
- let current = "";
4586
- let quote = null;
4587
- let escape = false;
4588
- for (let i = 0; i < input.length; i++) {
4589
- const char = input[i];
4590
- if (escape) {
4591
- current += char;
4592
- escape = false;
4593
- continue;
4594
- }
4595
- if (quote == null) {
4596
- if (/\s/.test(char)) {
4597
- if (current) {
4598
- tokens.push(current);
4599
- current = "";
4600
- }
4601
- continue;
4602
- }
4603
- if (char === "'" || char === '"') {
4604
- quote = char;
4605
- continue;
4606
- }
4607
- if (char === "\\") {
4608
- escape = true;
4609
- continue;
4610
- }
4611
- current += char;
4612
- continue;
4613
- }
4614
- if (quote === "'") {
4615
- if (char === "'") {
4616
- quote = null;
4617
- } else {
4618
- current += char;
4619
- }
4620
- continue;
4621
- }
4622
- if (char === '"') {
4623
- quote = null;
4624
- continue;
4625
- }
4626
- if (char === "\\") {
4627
- const next = input[++i];
4628
- if (next == null) {
4629
- throw new Error(`unterminated escape sequence in '${input}'`);
4630
- }
4631
- current += next;
4632
- continue;
4633
- }
4634
- current += char;
4635
- }
4636
- if (escape) {
4637
- throw new Error(`unterminated escape sequence in '${input}'`);
4638
- }
4639
- if (quote != null) {
4640
- throw new Error(`unterminated quoted string in '${input}'`);
4641
- }
4642
- if (current) {
4643
- tokens.push(current);
4644
- }
4645
- return tokens;
4646
- }
4647
- function stripLeadingFlags(value) {
4648
- const flags = {};
4649
- let remaining = value.trimStart();
4650
- while (remaining.startsWith("--")) {
4651
- const firstSpace = remaining.indexOf(" ");
4652
- if (firstSpace === -1) {
4653
- throw new Error(`invalid Dockerfile flag syntax: ${value}`);
4654
- }
4655
- const token = remaining.slice(0, firstSpace);
4656
- const rest = remaining.slice(firstSpace + 1).trimStart();
4657
- const flagBody = token.slice(2);
4658
- if (flagBody.includes("=")) {
4659
- const [key, flagValue2] = flagBody.split(/=(.*)/s, 2);
4660
- flags[key] = flagValue2;
4661
- remaining = rest;
4662
- continue;
4663
- }
4664
- const [flagValue, ...restTokens] = shellSplit(rest);
4665
- if (flagValue == null) {
4666
- throw new Error(`missing value for Dockerfile flag '${token}'`);
4667
- }
4668
- flags[flagBody] = flagValue;
4669
- remaining = restTokens.join(" ");
4670
- }
4671
- return { flags, remaining };
4672
- }
4673
- function parseFromValue(value, lineNumber) {
4674
- const { remaining } = stripLeadingFlags(value);
4675
- const tokens = shellSplit(remaining);
4676
- if (tokens.length === 0) {
4677
- throw new Error(`line ${lineNumber}: FROM must include a base image`);
4678
- }
4679
- if (tokens.length > 1 && tokens[1].toLowerCase() !== "as") {
4680
- throw new Error(`line ${lineNumber}: unsupported FROM syntax '${value}'`);
4522
+ function loadNativeBinding() {
4523
+ if (cachedNativeBinding) return cachedNativeBinding;
4524
+ if (cachedNativeBindingError) throw cachedNativeBindingError;
4525
+ try {
4526
+ const { loadNative } = resolveRequire()("../lib/runtime.cjs");
4527
+ cachedNativeBinding = loadNative();
4528
+ return cachedNativeBinding;
4529
+ } catch (error) {
4530
+ cachedNativeBindingError = error instanceof Error ? error : new Error(String(error));
4531
+ throw cachedNativeBindingError;
4681
4532
  }
4682
- return tokens[0];
4683
4533
  }
4684
- function buildPlanFromDockerfileText(dockerfileText, dockerfilePath, contextDir, registeredName) {
4685
- let baseImage;
4686
- const instructions = [];
4687
- for (const logicalLine of logicalDockerfileLines(dockerfileText)) {
4688
- const { keyword, value } = splitInstruction(
4689
- logicalLine.line,
4690
- logicalLine.lineNumber
4691
- );
4692
- if (keyword === "FROM") {
4693
- if (baseImage != null) {
4694
- throw new Error(
4695
- `line ${logicalLine.lineNumber}: multi-stage Dockerfiles are not supported for sandbox image creation`
4696
- );
4697
- }
4698
- baseImage = parseFromValue(value, logicalLine.lineNumber);
4699
- continue;
4700
- }
4701
- if (UNSUPPORTED_DOCKERFILE_INSTRUCTIONS.has(keyword)) {
4702
- throw new Error(
4703
- `line ${logicalLine.lineNumber}: Dockerfile instruction '${keyword}' is not supported for sandbox image creation`
4704
- );
4705
- }
4706
- instructions.push({
4707
- keyword,
4708
- value,
4709
- lineNumber: logicalLine.lineNumber
4710
- });
4711
- }
4712
- if (!baseImage) {
4713
- throw new Error("Dockerfile must contain a FROM instruction");
4714
- }
4715
- return {
4716
- dockerfilePath,
4717
- contextDir,
4718
- registeredName: registeredName ?? defaultRegisteredName(dockerfilePath),
4719
- dockerfileText,
4720
- baseImage,
4721
- instructions
4722
- };
4534
+ function __setNativeBindingForTest(binding) {
4535
+ cachedNativeBinding = binding;
4536
+ cachedNativeBindingError = void 0;
4723
4537
  }
4724
- async function loadDockerfilePlan(dockerfilePath, registeredName) {
4725
- const resolvedPath = path.resolve(dockerfilePath);
4726
- const fileStats = await stat(resolvedPath).catch(() => null);
4727
- if (!fileStats?.isFile()) {
4728
- throw new Error(`Dockerfile not found: ${dockerfilePath}`);
4729
- }
4730
- const dockerfileText = await readFile(resolvedPath, "utf8");
4731
- return buildPlanFromDockerfileText(
4732
- dockerfileText,
4733
- resolvedPath,
4734
- path.dirname(resolvedPath),
4735
- registeredName
4736
- );
4538
+ function nonEmpty(value) {
4539
+ return value && value.length > 0 ? value : void 0;
4737
4540
  }
4738
- function loadImagePlan(image, options = {}) {
4739
- const contextDir = path.resolve(options.contextDir ?? process.cwd());
4740
- const dockerfileText = dockerfileContent(image);
4741
- const logicalLines = logicalDockerfileLines(dockerfileText);
4742
- const instructions = image.baseImage == null ? logicalLines : logicalLines.slice(1);
4541
+ function buildContextFromEnv() {
4542
+ const debugRaw = (process.env.TENSORLAKE_DEBUG ?? "").toLowerCase();
4543
+ const debug = ["1", "true", "yes", "on"].includes(debugRaw);
4743
4544
  return {
4744
- dockerfilePath: path.join(contextDir, "Dockerfile"),
4745
- contextDir,
4746
- registeredName: options.registeredName ?? image.name,
4747
- dockerfileText,
4748
- baseImage: image.baseImage ?? void 0,
4749
- instructions: instructions.map(({ line, lineNumber }) => {
4750
- const parsed = splitInstruction(line, lineNumber);
4751
- return {
4752
- keyword: parsed.keyword,
4753
- value: parsed.value,
4754
- lineNumber
4755
- };
4756
- })
4545
+ apiUrl: nonEmpty(process.env.TENSORLAKE_API_URL) ?? "https://api.tensorlake.ai",
4546
+ apiKey: nonEmpty(process.env.TENSORLAKE_API_KEY),
4547
+ personalAccessToken: nonEmpty(process.env.TENSORLAKE_PAT),
4548
+ namespace: nonEmpty(process.env.INDEXIFY_NAMESPACE) ?? "default",
4549
+ organizationId: nonEmpty(process.env.TENSORLAKE_ORGANIZATION_ID),
4550
+ projectId: nonEmpty(process.env.TENSORLAKE_PROJECT_ID),
4551
+ debug
4757
4552
  };
4758
4553
  }
4759
- function ndjsonStdoutEmit(event) {
4760
- process.stdout.write(`${JSON.stringify(event)}
4761
- `);
4554
+ function defaultRegisteredName(dockerfilePath) {
4555
+ const parsed = path.parse(dockerfilePath);
4556
+ if (parsed.name.toLowerCase() === "dockerfile") {
4557
+ const parentName = path.basename(path.dirname(dockerfilePath)).trim();
4558
+ return parentName || "sandbox-image";
4559
+ }
4560
+ return parsed.name || "sandbox-image";
4762
4561
  }
4763
4562
  function noopEmit(_event) {
4764
4563
  }
4765
4564
  function stderrEmit(event) {
4766
- const type = typeof event.type === "string" ? event.type : "";
4767
4565
  const message = typeof event.message === "string" ? event.message : "";
4566
+ const type = typeof event.type === "string" ? event.type : "";
4768
4567
  if (type === "build_log") {
4769
4568
  const stream = typeof event.stream === "string" ? event.stream : "stdout";
4770
4569
  process.stderr.write(`[${stream}] ${message}
@@ -4774,557 +4573,103 @@ function stderrEmit(event) {
4774
4573
  `);
4775
4574
  }
4776
4575
  }
4777
- function debugEnabled() {
4778
- return ["1", "true", "yes", "on"].includes(
4779
- (process.env.TENSORLAKE_DEBUG ?? "").toLowerCase()
4780
- );
4576
+ function ndjsonStdoutEmit(event) {
4577
+ process.stdout.write(`${JSON.stringify(event)}
4578
+ `);
4781
4579
  }
4782
- function buildContextFromEnv() {
4783
- return {
4784
- apiUrl: process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai",
4785
- apiKey: process.env.TENSORLAKE_API_KEY,
4786
- personalAccessToken: process.env.TENSORLAKE_PAT,
4787
- namespace: process.env.INDEXIFY_NAMESPACE ?? "default",
4788
- organizationId: process.env.TENSORLAKE_ORGANIZATION_ID,
4789
- projectId: process.env.TENSORLAKE_PROJECT_ID,
4790
- debug: debugEnabled()
4580
+ function eventToEmitDict(event) {
4581
+ const out = {
4582
+ type: event.eventType,
4583
+ message: event.message
4791
4584
  };
4585
+ if (event.stream != null) out.stream = event.stream;
4586
+ return out;
4792
4587
  }
4793
- function createDefaultClient(context) {
4794
- const useScopeHeaders = context.personalAccessToken != null && context.apiKey == null;
4795
- return new SandboxClient({
4796
- apiUrl: context.apiUrl,
4797
- apiKey: context.apiKey ?? context.personalAccessToken,
4798
- organizationId: useScopeHeaders ? context.organizationId : void 0,
4799
- projectId: useScopeHeaders ? context.projectId : void 0,
4800
- namespace: context.namespace
4801
- });
4802
- }
4803
- function baseApiUrl(context) {
4804
- return context.apiUrl.replace(/\/+$/, "");
4805
- }
4806
- function scopedBuildsPath(context) {
4807
- return `/platform/v1/organizations/${encodeURIComponent(context.organizationId)}/projects/${encodeURIComponent(context.projectId)}/sandbox-template-builds`;
4808
- }
4809
- function platformHeaders(context) {
4810
- const headers = {
4811
- Authorization: `Bearer ${context.bearerToken}`,
4812
- "Content-Type": "application/json"
4813
- };
4814
- if (context.useScopeHeaders) {
4815
- headers["X-Forwarded-Organization-Id"] = context.organizationId;
4816
- headers["X-Forwarded-Project-Id"] = context.projectId;
4588
+ async function createSandboxImage(source, options = {}, deps = {}) {
4589
+ const emit = deps.emit ?? (options.verbose ? stderrEmit : noopEmit);
4590
+ const context = buildContextFromEnv();
4591
+ let dockerfilePath;
4592
+ let dockerfileText;
4593
+ let nativeContextDir;
4594
+ let effectiveName;
4595
+ if (source instanceof Image) {
4596
+ if (!source.baseImage) {
4597
+ throw new Error("Image must have a baseImage to build");
4598
+ }
4599
+ const resolvedContext = path.resolve(options.contextDir ?? process.cwd());
4600
+ dockerfilePath = path.join(resolvedContext, "Dockerfile");
4601
+ let text = dockerfileContent(source);
4602
+ if (!text.endsWith("\n")) text += "\n";
4603
+ dockerfileText = text;
4604
+ nativeContextDir = resolvedContext;
4605
+ effectiveName = options.registeredName ?? source.name;
4606
+ } else if (typeof source === "string") {
4607
+ const resolvedPath = path.resolve(source);
4608
+ if (!existsSync(resolvedPath)) {
4609
+ throw new Error(`Dockerfile not found: ${source}`);
4610
+ }
4611
+ dockerfilePath = resolvedPath;
4612
+ dockerfileText = void 0;
4613
+ nativeContextDir = void 0;
4614
+ effectiveName = options.registeredName ?? defaultRegisteredName(resolvedPath);
4615
+ } else {
4616
+ throw new TypeError(
4617
+ `source must be an Image or a Dockerfile path, got ${typeof source}`
4618
+ );
4817
4619
  }
4818
- return headers;
4819
- }
4820
- async function requestJson(url, init, errorPrefix) {
4821
- const response = await fetch(url, init);
4822
- if (!response.ok) {
4823
- throw new Error(
4824
- `${errorPrefix} (HTTP ${response.status}): ${await response.text()}`
4620
+ if (effectiveName === DEFAULT_IMAGE_NAME2) {
4621
+ console.warn(
4622
+ `Building sandbox image with the default name "${DEFAULT_IMAGE_NAME2}". Pass \`registeredName\` or \`Image({ name })\` to avoid collisions with other default-named images in this project.`
4825
4623
  );
4826
4624
  }
4827
- const text = await response.text();
4828
- return text ? JSON.parse(text) : {};
4829
- }
4830
- async function resolveBuildContext(context) {
4831
4625
  const bearerToken = context.apiKey ?? context.personalAccessToken;
4832
4626
  if (!bearerToken) {
4833
- throw new Error("Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT.");
4834
- }
4835
- if (context.apiKey) {
4836
- const scope = await requestJson(
4837
- `${baseApiUrl(context)}/platform/v1/keys/introspect`,
4838
- {
4839
- method: "POST",
4840
- headers: {
4841
- Authorization: `Bearer ${bearerToken}`,
4842
- "Content-Type": "application/json"
4843
- }
4844
- },
4845
- "API key introspection failed"
4846
- );
4847
- if (!scope.organizationId || !scope.projectId) {
4848
- throw new Error("API key introspection response is missing organizationId or projectId");
4849
- }
4850
- return {
4851
- ...context,
4852
- bearerToken,
4853
- organizationId: scope.organizationId,
4854
- projectId: scope.projectId,
4855
- useScopeHeaders: false
4856
- };
4857
- }
4858
- if (!context.organizationId || !context.projectId) {
4859
- throw new Error(
4860
- "Personal Access Token authentication requires TENSORLAKE_ORGANIZATION_ID and TENSORLAKE_PROJECT_ID to be set (e.g. via 'tl login && tl init'). To skip this requirement, authenticate with TENSORLAKE_API_KEY instead \u2014 API keys are bound to a single project at creation."
4861
- );
4627
+ throw new Error("Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT credentials.");
4862
4628
  }
4863
- return {
4864
- ...context,
4865
- bearerToken,
4866
- organizationId: context.organizationId,
4867
- projectId: context.projectId,
4868
- useScopeHeaders: true
4869
- };
4870
- }
4871
- async function prepareRootfsBuild(context, plan, isPublic) {
4872
- if (!plan.baseImage) {
4873
- throw new Error("Sandbox image builds require a Dockerfile FROM image or Image baseImage");
4874
- }
4875
- const spec = await requestJson(
4876
- `${baseApiUrl(context)}${scopedBuildsPath(context)}`,
4629
+ emit({ type: "status", message: `Building image '${effectiveName}'...` });
4630
+ const binding = loadNativeBinding();
4631
+ let emitError;
4632
+ const resultJson = await binding.buildSandboxImage(
4877
4633
  {
4878
- method: "POST",
4879
- headers: platformHeaders(context),
4880
- body: JSON.stringify({
4881
- name: plan.registeredName,
4882
- dockerfile: plan.dockerfileText,
4883
- baseImage: plan.baseImage,
4884
- public: isPublic
4885
- })
4886
- },
4887
- "failed to prepare sandbox image build"
4888
- );
4889
- return { prepared: parsePreparedBuild(spec), spec };
4890
- }
4891
- function parsePreparedBuild(raw) {
4892
- const builder = raw.builder;
4893
- if (!builder) {
4894
- throw new Error("platform API response is missing rootfs builder configuration");
4895
- }
4896
- const prepared = {
4897
- ...raw,
4898
- buildId: requiredString(raw, "buildId"),
4899
- snapshotId: requiredString(raw, "snapshotId"),
4900
- snapshotUri: requiredString(raw, "snapshotUri"),
4901
- rootfsNodeKind: requiredString(raw, "rootfsNodeKind"),
4902
- builder: {
4903
- image: requiredString(builder, "image"),
4904
- command: requiredString(builder, "command"),
4905
- cpus: requiredNumber(builder, "cpus"),
4906
- memoryMb: requiredNumber(builder, "memoryMb"),
4907
- diskMb: requiredNumber(builder, "diskMb")
4908
- }
4909
- };
4910
- const parent = raw.parent;
4911
- if (parent != null) {
4912
- prepared.parent = {
4913
- parentManifestUri: requiredString(parent, "parentManifestUri"),
4914
- rootfsDiskBytes: optionalNumber(parent, "rootfsDiskBytes")
4915
- };
4916
- }
4917
- return prepared;
4918
- }
4919
- async function completeRootfsBuild(context, buildId, request) {
4920
- return requestJson(
4921
- `${baseApiUrl(context)}${scopedBuildsPath(context)}/${encodeURIComponent(buildId)}/complete`,
4922
- {
4923
- method: "POST",
4924
- headers: platformHeaders(context),
4925
- body: JSON.stringify(request)
4634
+ apiUrl: context.apiUrl,
4635
+ bearerToken,
4636
+ dockerfilePath,
4637
+ registeredName: effectiveName,
4638
+ diskMb: options.diskMb,
4639
+ builderDiskMb: options.builderDiskMb,
4640
+ cpus: options.cpus,
4641
+ memoryMb: options.memoryMb,
4642
+ isPublic: options.isPublic ?? false,
4643
+ organizationId: context.organizationId,
4644
+ projectId: context.projectId,
4645
+ namespace: context.namespace,
4646
+ useScopeHeaders: context.personalAccessToken != null && context.apiKey == null,
4647
+ userAgent: void 0,
4648
+ dockerfileText,
4649
+ contextDir: nativeContextDir
4926
4650
  },
4927
- "failed to complete sandbox image build"
4928
- );
4929
- }
4930
- async function resolvedDockerConfigJson() {
4931
- const configDir = process.env.DOCKER_CONFIG ?? path.join(homedir(), ".docker");
4932
- const configPath = path.join(configDir, "config.json");
4933
- try {
4934
- const content = await readFile(configPath, "utf8");
4935
- const parsed = JSON.parse(content);
4936
- const auths = parsed.auths;
4937
- if (auths != null && Object.keys(auths).length > 0) {
4938
- return JSON.stringify({ auths });
4939
- }
4940
- } catch (error) {
4941
- if (error.code === "ENOENT") {
4942
- return void 0;
4943
- }
4944
- throw error;
4945
- }
4946
- return void 0;
4947
- }
4948
- function rootfsDiskBytes(diskMb, prepared) {
4949
- if (diskMb != null) {
4950
- return diskMb * 1024 * 1024;
4951
- }
4952
- if (prepared.parent != null) {
4953
- if (prepared.parent.rootfsDiskBytes == null) {
4954
- throw new Error(
4955
- "platform API did not return parent rootfsDiskBytes for diff build; pass diskMb explicitly or update Platform API"
4956
- );
4957
- }
4958
- return prepared.parent.rootfsDiskBytes;
4959
- }
4960
- return DEFAULT_ROOTFS_DISK_MB * 1024 * 1024;
4961
- }
4962
- function rootfsDiskBytesToMb(bytes) {
4963
- return Math.ceil(bytes / (1024 * 1024));
4964
- }
4965
- async function buildRootfsSpec(preparedSpec, prepared, plan, diskMb) {
4966
- const spec = {
4967
- ...preparedSpec,
4968
- dockerfile: plan.dockerfileText,
4969
- contextDir: REMOTE_CONTEXT_DIR,
4970
- baseImage: plan.baseImage,
4971
- rootfsDiskBytes: rootfsDiskBytes(diskMb, prepared)
4972
- };
4973
- const dockerConfigJson = await resolvedDockerConfigJson();
4974
- if (dockerConfigJson != null) {
4975
- spec.dockerConfigJson = dockerConfigJson;
4976
- }
4977
- return spec;
4978
- }
4979
- function rootfsBuilderExecutable(executable) {
4980
- return executable === ROOTFS_BUILDER_COMMAND ? `${ROOTFS_BUILDER_BIN_DIR}/${ROOTFS_BUILDER_COMMAND}` : executable;
4981
- }
4982
- function rootfsBuilderEnv() {
4983
- return { PATH: ROOTFS_BUILDER_PATH };
4984
- }
4985
- async function runRootfsBuilder(sandbox, command, emit, sleep3) {
4986
- const parts = shellSplit(command);
4987
- const [executable, ...commandArgs] = parts;
4988
- if (!executable) {
4989
- throw new Error("empty rootfs builder command returned by platform API");
4990
- }
4991
- await runStreaming(
4992
- sandbox,
4993
- emit,
4994
- sleep3,
4995
- rootfsBuilderExecutable(executable),
4996
- [...commandArgs, "--spec", REMOTE_SPEC_PATH, "--metadata-out", REMOTE_METADATA_PATH],
4997
- rootfsBuilderEnv(),
4998
- REMOTE_BUILD_DIR
4999
- );
5000
- }
5001
- function metadataString(metadata, snakeKey, camelKey) {
5002
- const value = metadata[snakeKey] ?? metadata[camelKey];
5003
- return typeof value === "string" ? value : void 0;
5004
- }
5005
- function metadataNumber(metadata, snakeKey, camelKey) {
5006
- const value = metadata[snakeKey] ?? metadata[camelKey];
5007
- if (typeof value === "number") {
5008
- return value;
5009
- }
5010
- if (typeof value === "string" && value.trim()) {
5011
- const parsed = Number(value);
5012
- return Number.isFinite(parsed) ? parsed : void 0;
5013
- }
5014
- return void 0;
5015
- }
5016
- function completeRequestFromMetadata(prepared, metadata) {
5017
- const rootfsNodeKind = metadataString(metadata, "rootfs_node_kind", "rootfsNodeKind") ?? prepared.rootfsNodeKind;
5018
- const parentManifestUri = metadataString(metadata, "parent_manifest_uri", "parentManifestUri") ?? (rootfsNodeKind === "diff" ? prepared.parent?.parentManifestUri : void 0);
5019
- if (rootfsNodeKind === "diff" && parentManifestUri == null) {
5020
- throw new Error("rootfs diff build completed without parent_manifest_uri");
5021
- }
5022
- const snapshotFormatVersion = metadataString(
5023
- metadata,
5024
- "snapshot_format_version",
5025
- "snapshotFormatVersion"
5026
- );
5027
- const snapshotSizeBytes = metadataNumber(
5028
- metadata,
5029
- "snapshot_size_bytes",
5030
- "snapshotSizeBytes"
5031
- );
5032
- const rootfsDiskBytesValue = metadataNumber(
5033
- metadata,
5034
- "rootfs_disk_bytes",
5035
- "rootfsDiskBytes"
5036
- );
5037
- if (!snapshotFormatVersion) {
5038
- throw new Error("rootfs builder metadata is missing snapshot_format_version");
5039
- }
5040
- if (snapshotSizeBytes == null) {
5041
- throw new Error("rootfs builder metadata is missing numeric snapshot_size_bytes");
5042
- }
5043
- if (rootfsDiskBytesValue == null) {
5044
- throw new Error("rootfs builder metadata is missing numeric rootfs_disk_bytes");
5045
- }
5046
- return {
5047
- snapshotId: metadataString(metadata, "snapshot_id", "snapshotId") ?? prepared.snapshotId,
5048
- snapshotUri: metadataString(metadata, "snapshot_uri", "snapshotUri") ?? prepared.snapshotUri,
5049
- snapshotFormatVersion,
5050
- snapshotSizeBytes,
5051
- rootfsDiskBytes: rootfsDiskBytesValue,
5052
- rootfsNodeKind,
5053
- ...parentManifestUri ? { parentManifestUri } : {}
5054
- };
5055
- }
5056
- function requiredString(object, key) {
5057
- const value = object[key];
5058
- if (typeof value !== "string" || value.length === 0) {
5059
- throw new Error(`expected '${key}' to be a non-empty string`);
5060
- }
5061
- return value;
5062
- }
5063
- function requiredNumber(object, key) {
5064
- const value = object[key];
5065
- if (typeof value !== "number" || !Number.isFinite(value)) {
5066
- throw new Error(`expected '${key}' to be a finite number`);
5067
- }
5068
- return value;
5069
- }
5070
- function optionalNumber(object, key) {
5071
- const value = object[key];
5072
- if (value == null) {
5073
- return void 0;
5074
- }
5075
- if (typeof value !== "number" || !Number.isFinite(value)) {
5076
- throw new Error(`expected '${key}' to be a finite number`);
5077
- }
5078
- return value;
5079
- }
5080
- async function runChecked(sandbox, command, args, env, workingDir, user) {
5081
- const options = {
5082
- args,
5083
- env,
5084
- workingDir
5085
- };
5086
- if (user != null) {
5087
- options.user = user;
5088
- }
5089
- const result = await sandbox.run(command, options);
5090
- if (result.exitCode !== 0) {
5091
- throw new Error(
5092
- `Command '${command} ${args.join(" ")}' failed with exit code ${result.exitCode}`
5093
- );
5094
- }
5095
- return result;
5096
- }
5097
- async function runStreaming(sandbox, emit, sleep3, command, args = [], env, workingDir) {
5098
- const proc = await sandbox.startProcess(command, {
5099
- args,
5100
- env,
5101
- workingDir,
5102
- user: ROOTFS_BUILDER_PROCESS_USER
5103
- });
5104
- let stdoutSeen = 0;
5105
- let stderrSeen = 0;
5106
- let info;
5107
- while (true) {
5108
- const stdoutResp = await sandbox.getStdout(proc.pid);
5109
- emitOutputLines(emit, "stdout", stdoutResp, stdoutSeen);
5110
- stdoutSeen = stdoutResp.lines.length;
5111
- const stderrResp = await sandbox.getStderr(proc.pid);
5112
- emitOutputLines(emit, "stderr", stderrResp, stderrSeen);
5113
- stderrSeen = stderrResp.lines.length;
5114
- info = await sandbox.getProcess(proc.pid);
5115
- if (info.status !== "running" /* RUNNING */) {
5116
- const finalStdout = await sandbox.getStdout(proc.pid);
5117
- emitOutputLines(emit, "stdout", finalStdout, stdoutSeen);
5118
- stdoutSeen = finalStdout.lines.length;
5119
- const finalStderr = await sandbox.getStderr(proc.pid);
5120
- emitOutputLines(emit, "stderr", finalStderr, stderrSeen);
5121
- break;
5122
- }
5123
- await sleep3(300);
5124
- }
5125
- for (let i = 0; i < 10; i++) {
5126
- if (info.exitCode != null || info.signal != null) {
5127
- break;
4651
+ (event) => {
4652
+ try {
4653
+ emit(eventToEmitDict(event));
4654
+ } catch (error) {
4655
+ emitError ??= error;
4656
+ }
5128
4657
  }
5129
- await sleep3(200);
5130
- info = await sandbox.getProcess(proc.pid);
5131
- }
5132
- const exitCode = info.exitCode != null ? info.exitCode : info.signal != null ? -info.signal : 0;
5133
- if (exitCode !== 0) {
5134
- throw new Error(
5135
- `Command '${command} ${args.join(" ")}' failed with exit code ${exitCode}`
5136
- );
5137
- }
5138
- }
5139
- function emitOutputLines(emit, stream, response, seen) {
5140
- for (const line of response.lines.slice(seen)) {
5141
- emit({ type: "build_log", stream, message: line });
5142
- }
5143
- }
5144
- async function ensureRemoteBuildRoot(sandbox) {
5145
- await runChecked(
5146
- sandbox,
5147
- "mkdir",
5148
- ["-p", REMOTE_BUILD_DIR],
5149
- void 0,
5150
- void 0,
5151
- ROOTFS_BUILDER_PROCESS_USER
5152
4658
  );
5153
- await runChecked(
5154
- sandbox,
5155
- "chmod",
5156
- ["0777", REMOTE_BUILD_DIR],
5157
- void 0,
5158
- void 0,
5159
- ROOTFS_BUILDER_PROCESS_USER
5160
- );
5161
- }
5162
- async function ensureRemoteDirectory(sandbox, remotePath) {
5163
- await runChecked(sandbox, "mkdir", ["-p", remotePath]);
5164
- }
5165
- async function ensureRemoteParentDir(sandbox, remotePath) {
5166
- await ensureRemoteDirectory(sandbox, path.posix.dirname(remotePath));
5167
- }
5168
- async function copyLocalPathToSandbox(sandbox, localPath, remotePath) {
5169
- const fileStats = await stat(localPath).catch(() => null);
5170
- if (!fileStats) {
5171
- throw new Error(`Local path not found: ${localPath}`);
5172
- }
5173
- if (fileStats.isFile()) {
5174
- await ensureRemoteParentDir(sandbox, remotePath);
5175
- await sandbox.writeFile(remotePath, await readFile(localPath));
5176
- return;
5177
- }
5178
- if (!fileStats.isDirectory()) {
5179
- throw new Error(`Local path not found: ${localPath}`);
5180
- }
5181
- const entries = await readdir(localPath, { withFileTypes: true });
5182
- for (const entry of entries) {
5183
- const sourcePath = path.join(localPath, entry.name);
5184
- const destinationPath = path.posix.join(remotePath, entry.name);
5185
- if (entry.isDirectory()) {
5186
- await ensureRemoteDirectory(sandbox, destinationPath);
5187
- await copyLocalPathToSandbox(sandbox, sourcePath, destinationPath);
5188
- } else if (entry.isFile()) {
5189
- await ensureRemoteParentDir(sandbox, destinationPath);
5190
- await sandbox.writeFile(destinationPath, await readFile(sourcePath));
5191
- }
5192
- }
5193
- }
5194
- async function registerImage(context, name, dockerfile, snapshotId, snapshotSandboxId, snapshotUri, snapshotSizeBytes, rootfsDiskBytes2, isPublic, snapshotFormatVersion) {
5195
- const bearerToken = context.apiKey ?? context.personalAccessToken;
5196
- if (!bearerToken) {
5197
- throw new Error("Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT.");
4659
+ if (emitError != null) {
4660
+ throw emitError;
5198
4661
  }
5199
- const baseUrl = context.apiUrl.replace(/\/+$/, "");
5200
- const headers = {
5201
- Authorization: `Bearer ${bearerToken}`,
5202
- "Content-Type": "application/json"
5203
- };
5204
- let url;
5205
- if (context.apiKey) {
5206
- url = `${baseUrl}/platform/v1/sandbox-templates`;
5207
- } else {
5208
- if (!context.organizationId || !context.projectId) {
5209
- throw new Error(
5210
- "Personal Access Token authentication requires TENSORLAKE_ORGANIZATION_ID and TENSORLAKE_PROJECT_ID to be set (e.g. via 'tl login && tl init'). To skip this requirement, authenticate with TENSORLAKE_API_KEY instead \u2014 API keys are bound to a single project at creation."
5211
- );
5212
- }
5213
- url = `${baseUrl}/platform/v1/organizations/${encodeURIComponent(context.organizationId)}/projects/${encodeURIComponent(context.projectId)}/sandbox-templates`;
5214
- headers["X-Forwarded-Organization-Id"] = context.organizationId;
5215
- headers["X-Forwarded-Project-Id"] = context.projectId;
5216
- }
5217
- const response = await fetch(url, {
5218
- method: "POST",
5219
- headers,
5220
- body: JSON.stringify({
5221
- name,
5222
- dockerfile,
5223
- snapshotId,
5224
- snapshotSandboxId,
5225
- snapshotUri,
5226
- ...snapshotFormatVersion ? { snapshotFormatVersion } : {},
5227
- snapshotSizeBytes,
5228
- rootfsDiskBytes: rootfsDiskBytes2,
5229
- public: isPublic
5230
- })
5231
- });
5232
- if (!response.ok) {
5233
- throw new Error(
5234
- `${response.status} ${response.statusText}: ${await response.text()}`
5235
- );
4662
+ let result = {};
4663
+ if (resultJson.trim().length > 0) {
4664
+ result = JSON.parse(resultJson);
5236
4665
  }
5237
- const text = await response.text();
5238
- return text ? JSON.parse(text) : {};
5239
- }
5240
- async function createSandboxImage(source, options = {}, deps = {}) {
5241
- const emit = deps.emit ?? (options.verbose ? stderrEmit : noopEmit);
5242
- const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
5243
- const context = buildContextFromEnv();
5244
- const clientFactory = deps.createClient ?? createDefaultClient;
5245
- const sourceLabel = typeof source === "string" ? source : `Image(${source.name})`;
5246
- emit({ type: "status", message: `Loading ${sourceLabel}...` });
5247
- const plan = typeof source === "string" ? await loadDockerfilePlan(source, options.registeredName) : loadImagePlan(source, options);
5248
4666
  emit({
5249
- type: "status",
5250
- message: `Selected image name: ${plan.registeredName}`
4667
+ type: "image_registered",
4668
+ name: effectiveName,
4669
+ image_id: typeof result.id === "string" && result.id || typeof result.templateId === "string" && result.templateId || ""
5251
4670
  });
5252
- emit({ type: "status", message: "Preparing rootfs build..." });
5253
- const resolvedContext = await resolveBuildContext(context);
5254
- const { prepared, spec: preparedSpec } = await prepareRootfsBuild(
5255
- resolvedContext,
5256
- plan,
5257
- options.isPublic ?? false
5258
- );
5259
- emit({
5260
- type: "status",
5261
- message: prepared.rootfsNodeKind === "diff" ? "Build mode: RootfsDiff" : "Build mode: RootfsBase"
5262
- });
5263
- const client = clientFactory(context);
5264
- let sandbox;
5265
- try {
5266
- const outputRootfsDiskBytes = rootfsDiskBytes(options.diskMb, prepared);
5267
- const builderDiskMb = Math.max(
5268
- rootfsDiskBytesToMb(outputRootfsDiskBytes),
5269
- options.builderDiskMb ?? prepared.builder.diskMb
5270
- );
5271
- emit({
5272
- type: "status",
5273
- message: `Creating rootfs builder sandbox from ${prepared.builder.image}...`
5274
- });
5275
- sandbox = await client.createAndConnect({
5276
- image: prepared.builder.image,
5277
- cpus: options.cpus ?? prepared.builder.cpus,
5278
- memoryMb: options.memoryMb ?? prepared.builder.memoryMb,
5279
- diskMb: builderDiskMb
5280
- });
5281
- emit({
5282
- type: "status",
5283
- message: `Rootfs builder sandbox ${sandbox.sandboxId} is running`
5284
- });
5285
- emit({ type: "status", message: "Uploading build context..." });
5286
- await ensureRemoteBuildRoot(sandbox);
5287
- await copyLocalPathToSandbox(sandbox, plan.contextDir, REMOTE_CONTEXT_DIR);
5288
- const spec = await buildRootfsSpec(
5289
- preparedSpec,
5290
- prepared,
5291
- plan,
5292
- options.diskMb
5293
- );
5294
- await ensureRemoteParentDir(sandbox, REMOTE_SPEC_PATH);
5295
- await sandbox.writeFile(
5296
- REMOTE_SPEC_PATH,
5297
- new TextEncoder().encode(JSON.stringify(spec, null, 2))
5298
- );
5299
- emit({ type: "status", message: "Running offline rootfs builder..." });
5300
- await runRootfsBuilder(sandbox, prepared.builder.command, emit, sleep3);
5301
- const metadataBytes = await sandbox.readFile(REMOTE_METADATA_PATH);
5302
- const metadata = JSON.parse(
5303
- new TextDecoder().decode(metadataBytes)
5304
- );
5305
- const completeRequest = completeRequestFromMetadata(prepared, metadata);
5306
- emit({ type: "status", message: "Completing image registration..." });
5307
- const result = await completeRootfsBuild(
5308
- resolvedContext,
5309
- prepared.buildId,
5310
- completeRequest
5311
- );
5312
- emit({
5313
- type: "image_registered",
5314
- name: plan.registeredName,
5315
- image_id: typeof result.id === "string" && result.id || typeof result.templateId === "string" && result.templateId || ""
5316
- });
5317
- emit({ type: "done" });
5318
- return result;
5319
- } finally {
5320
- if (sandbox) {
5321
- try {
5322
- await sandbox.terminate();
5323
- } catch {
5324
- }
5325
- }
5326
- client.close();
5327
- }
4671
+ emit({ type: "done" });
4672
+ return result;
5328
4673
  }
5329
4674
  async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
5330
4675
  const parsed = parseArgs({
@@ -5341,7 +4686,9 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
5341
4686
  });
5342
4687
  const dockerfilePath = parsed.positionals[0];
5343
4688
  if (!dockerfilePath) {
5344
- throw new Error("Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--disk_mb MB] [--builder_disk_mb MB] [--public]");
4689
+ throw new Error(
4690
+ "Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--disk_mb MB] [--builder_disk_mb MB] [--public]"
4691
+ );
5345
4692
  }
5346
4693
  const cpus = parsed.values.cpus != null ? Number(parsed.values.cpus) : void 0;
5347
4694
  const memoryMb = parsed.values.memory != null ? Number(parsed.values.memory) : void 0;
@@ -5374,28 +4721,12 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
5374
4721
  { emit: ndjsonStdoutEmit }
5375
4722
  );
5376
4723
  }
5377
- var DEFAULT_ROOTFS_DISK_MB, REMOTE_BUILD_DIR, REMOTE_CONTEXT_DIR, REMOTE_SPEC_PATH, REMOTE_METADATA_PATH, ROOTFS_BUILDER_BIN_DIR, ROOTFS_BUILDER_PATH, ROOTFS_BUILDER_COMMAND, ROOTFS_BUILDER_PROCESS_USER, UNSUPPORTED_DOCKERFILE_INSTRUCTIONS;
4724
+ var cachedNativeBinding, cachedNativeBindingError, DEFAULT_IMAGE_NAME2;
5378
4725
  var init_sandbox_image = __esm({
5379
4726
  "src/sandbox-image.ts"() {
5380
4727
  "use strict";
5381
- init_models();
5382
- init_client();
5383
4728
  init_image();
5384
- DEFAULT_ROOTFS_DISK_MB = 10 * 1024;
5385
- REMOTE_BUILD_DIR = "/var/lib/tensorlake/rootfs-builder/build";
5386
- REMOTE_CONTEXT_DIR = "/var/lib/tensorlake/rootfs-builder/build/context";
5387
- REMOTE_SPEC_PATH = "/var/lib/tensorlake/rootfs-builder/build/spec.json";
5388
- REMOTE_METADATA_PATH = "/var/lib/tensorlake/rootfs-builder/build/metadata.json";
5389
- ROOTFS_BUILDER_BIN_DIR = "/usr/local/bin";
5390
- ROOTFS_BUILDER_PATH = "/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
5391
- ROOTFS_BUILDER_COMMAND = "tl-rootfs-build";
5392
- ROOTFS_BUILDER_PROCESS_USER = "root";
5393
- UNSUPPORTED_DOCKERFILE_INSTRUCTIONS = /* @__PURE__ */ new Set([
5394
- "ARG",
5395
- "ONBUILD",
5396
- "SHELL",
5397
- "USER"
5398
- ]);
4729
+ DEFAULT_IMAGE_NAME2 = "default";
5399
4730
  }
5400
4731
  });
5401
4732