tensorlake 0.5.10 → 0.5.11

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.cjs CHANGED
@@ -35,7 +35,7 @@ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TI
35
35
  var init_defaults = __esm({
36
36
  "src/defaults.ts"() {
37
37
  "use strict";
38
- SDK_VERSION = "0.5.10";
38
+ SDK_VERSION = "0.5.11";
39
39
  API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
40
40
  API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
41
41
  NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
@@ -4604,12 +4604,6 @@ function shellSplit(input) {
4604
4604
  }
4605
4605
  return tokens;
4606
4606
  }
4607
- function shellQuote(value) {
4608
- if (!value) {
4609
- return "''";
4610
- }
4611
- return `'${value.replace(/'/g, `'\\''`)}'`;
4612
- }
4613
4607
  function stripLeadingFlags(value) {
4614
4608
  const flags = {};
4615
4609
  let remaining = value.trimStart();
@@ -4647,75 +4641,6 @@ function parseFromValue(value, lineNumber) {
4647
4641
  }
4648
4642
  return tokens[0];
4649
4643
  }
4650
- function parseCopyLikeValues(value, lineNumber, keyword) {
4651
- const { flags, remaining } = stripLeadingFlags(value);
4652
- if ("from" in flags) {
4653
- throw new Error(
4654
- `line ${lineNumber}: ${keyword} --from is not supported for sandbox image creation`
4655
- );
4656
- }
4657
- const payload = remaining.trim();
4658
- if (!payload) {
4659
- throw new Error(
4660
- `line ${lineNumber}: ${keyword} must include source and destination`
4661
- );
4662
- }
4663
- let parts;
4664
- if (payload.startsWith("[")) {
4665
- let parsed;
4666
- try {
4667
- parsed = JSON.parse(payload);
4668
- } catch (error) {
4669
- throw new Error(
4670
- `line ${lineNumber}: invalid JSON array syntax for ${keyword}: ${error.message}`
4671
- );
4672
- }
4673
- if (!Array.isArray(parsed) || parsed.length < 2 || parsed.some((item) => typeof item !== "string")) {
4674
- throw new Error(
4675
- `line ${lineNumber}: ${keyword} JSON array form requires at least two string values`
4676
- );
4677
- }
4678
- parts = parsed;
4679
- } else {
4680
- parts = shellSplit(payload);
4681
- if (parts.length < 2) {
4682
- throw new Error(
4683
- `line ${lineNumber}: ${keyword} must include at least one source and one destination`
4684
- );
4685
- }
4686
- }
4687
- return {
4688
- flags,
4689
- sources: parts.slice(0, -1),
4690
- destination: parts[parts.length - 1]
4691
- };
4692
- }
4693
- function parseEnvPairs(value, lineNumber) {
4694
- const tokens = shellSplit(value);
4695
- if (tokens.length === 0) {
4696
- throw new Error(`line ${lineNumber}: ENV must include a key and value`);
4697
- }
4698
- if (tokens.every((token) => token.includes("="))) {
4699
- return tokens.map((token) => {
4700
- const [key, envValue] = token.split(/=(.*)/s, 2);
4701
- if (!key) {
4702
- throw new Error(`line ${lineNumber}: invalid ENV token '${token}'`);
4703
- }
4704
- return [key, envValue];
4705
- });
4706
- }
4707
- if (tokens.length < 2) {
4708
- throw new Error(`line ${lineNumber}: ENV must include a key and value`);
4709
- }
4710
- return [[tokens[0], tokens.slice(1).join(" ")]];
4711
- }
4712
- function resolveContainerPath(containerPath, workingDir) {
4713
- if (!containerPath) {
4714
- return workingDir;
4715
- }
4716
- const normalized = containerPath.startsWith("/") ? import_node_path.default.posix.normalize(containerPath) : import_node_path.default.posix.normalize(import_node_path.default.posix.join(workingDir, containerPath));
4717
- return normalized.startsWith("/") ? normalized : `/${normalized}`;
4718
- }
4719
4644
  function buildPlanFromDockerfileText(dockerfileText, dockerfilePath, contextDir, registeredName) {
4720
4645
  let baseImage;
4721
4646
  const instructions = [];
@@ -4826,14 +4751,292 @@ function buildContextFromEnv() {
4826
4751
  };
4827
4752
  }
4828
4753
  function createDefaultClient(context) {
4754
+ const useScopeHeaders = context.personalAccessToken != null && context.apiKey == null;
4829
4755
  return new SandboxClient({
4830
4756
  apiUrl: context.apiUrl,
4831
4757
  apiKey: context.apiKey ?? context.personalAccessToken,
4832
- organizationId: context.organizationId,
4833
- projectId: context.projectId,
4758
+ organizationId: useScopeHeaders ? context.organizationId : void 0,
4759
+ projectId: useScopeHeaders ? context.projectId : void 0,
4834
4760
  namespace: context.namespace
4835
4761
  });
4836
4762
  }
4763
+ function baseApiUrl(context) {
4764
+ return context.apiUrl.replace(/\/+$/, "");
4765
+ }
4766
+ function scopedBuildsPath(context) {
4767
+ return `/platform/v1/organizations/${encodeURIComponent(context.organizationId)}/projects/${encodeURIComponent(context.projectId)}/sandbox-template-builds`;
4768
+ }
4769
+ function platformHeaders(context) {
4770
+ const headers = {
4771
+ Authorization: `Bearer ${context.bearerToken}`,
4772
+ "Content-Type": "application/json"
4773
+ };
4774
+ if (context.useScopeHeaders) {
4775
+ headers["X-Forwarded-Organization-Id"] = context.organizationId;
4776
+ headers["X-Forwarded-Project-Id"] = context.projectId;
4777
+ }
4778
+ return headers;
4779
+ }
4780
+ async function requestJson(url, init, errorPrefix) {
4781
+ const response = await fetch(url, init);
4782
+ if (!response.ok) {
4783
+ throw new Error(
4784
+ `${errorPrefix} (HTTP ${response.status}): ${await response.text()}`
4785
+ );
4786
+ }
4787
+ const text = await response.text();
4788
+ return text ? JSON.parse(text) : {};
4789
+ }
4790
+ async function resolveBuildContext(context) {
4791
+ const bearerToken = context.apiKey ?? context.personalAccessToken;
4792
+ if (!bearerToken) {
4793
+ throw new Error("Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT.");
4794
+ }
4795
+ if (context.apiKey) {
4796
+ const scope = await requestJson(
4797
+ `${baseApiUrl(context)}/platform/v1/keys/introspect`,
4798
+ {
4799
+ method: "POST",
4800
+ headers: {
4801
+ Authorization: `Bearer ${bearerToken}`,
4802
+ "Content-Type": "application/json"
4803
+ }
4804
+ },
4805
+ "API key introspection failed"
4806
+ );
4807
+ if (!scope.organizationId || !scope.projectId) {
4808
+ throw new Error("API key introspection response is missing organizationId or projectId");
4809
+ }
4810
+ return {
4811
+ ...context,
4812
+ bearerToken,
4813
+ organizationId: scope.organizationId,
4814
+ projectId: scope.projectId,
4815
+ useScopeHeaders: false
4816
+ };
4817
+ }
4818
+ if (!context.organizationId || !context.projectId) {
4819
+ throw new Error(
4820
+ "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."
4821
+ );
4822
+ }
4823
+ return {
4824
+ ...context,
4825
+ bearerToken,
4826
+ organizationId: context.organizationId,
4827
+ projectId: context.projectId,
4828
+ useScopeHeaders: true
4829
+ };
4830
+ }
4831
+ async function prepareRootfsBuild(context, plan, isPublic) {
4832
+ if (!plan.baseImage) {
4833
+ throw new Error("Sandbox image builds require a Dockerfile FROM image or Image baseImage");
4834
+ }
4835
+ const spec = await requestJson(
4836
+ `${baseApiUrl(context)}${scopedBuildsPath(context)}`,
4837
+ {
4838
+ method: "POST",
4839
+ headers: platformHeaders(context),
4840
+ body: JSON.stringify({
4841
+ name: plan.registeredName,
4842
+ dockerfile: plan.dockerfileText,
4843
+ baseImage: plan.baseImage,
4844
+ public: isPublic
4845
+ })
4846
+ },
4847
+ "failed to prepare sandbox image build"
4848
+ );
4849
+ return { prepared: parsePreparedBuild(spec), spec };
4850
+ }
4851
+ function parsePreparedBuild(raw) {
4852
+ const builder = raw.builder;
4853
+ if (!builder) {
4854
+ throw new Error("platform API response is missing rootfs builder configuration");
4855
+ }
4856
+ const prepared = {
4857
+ ...raw,
4858
+ buildId: requiredString(raw, "buildId"),
4859
+ snapshotId: requiredString(raw, "snapshotId"),
4860
+ snapshotUri: requiredString(raw, "snapshotUri"),
4861
+ rootfsNodeKind: requiredString(raw, "rootfsNodeKind"),
4862
+ builder: {
4863
+ image: requiredString(builder, "image"),
4864
+ command: requiredString(builder, "command"),
4865
+ cpus: requiredNumber(builder, "cpus"),
4866
+ memoryMb: requiredNumber(builder, "memoryMb"),
4867
+ diskMb: requiredNumber(builder, "diskMb")
4868
+ }
4869
+ };
4870
+ const parent = raw.parent;
4871
+ if (parent != null) {
4872
+ prepared.parent = {
4873
+ parentManifestUri: requiredString(parent, "parentManifestUri"),
4874
+ rootfsDiskBytes: optionalNumber(parent, "rootfsDiskBytes")
4875
+ };
4876
+ }
4877
+ return prepared;
4878
+ }
4879
+ async function completeRootfsBuild(context, buildId, request) {
4880
+ return requestJson(
4881
+ `${baseApiUrl(context)}${scopedBuildsPath(context)}/${encodeURIComponent(buildId)}/complete`,
4882
+ {
4883
+ method: "POST",
4884
+ headers: platformHeaders(context),
4885
+ body: JSON.stringify(request)
4886
+ },
4887
+ "failed to complete sandbox image build"
4888
+ );
4889
+ }
4890
+ async function resolvedDockerConfigJson() {
4891
+ const configDir = process.env.DOCKER_CONFIG ?? import_node_path.default.join((0, import_node_os.homedir)(), ".docker");
4892
+ const configPath = import_node_path.default.join(configDir, "config.json");
4893
+ try {
4894
+ const content = await (0, import_promises.readFile)(configPath, "utf8");
4895
+ const parsed = JSON.parse(content);
4896
+ const auths = parsed.auths;
4897
+ if (auths != null && Object.keys(auths).length > 0) {
4898
+ return JSON.stringify({ auths });
4899
+ }
4900
+ } catch (error) {
4901
+ if (error.code === "ENOENT") {
4902
+ return void 0;
4903
+ }
4904
+ throw error;
4905
+ }
4906
+ return void 0;
4907
+ }
4908
+ function rootfsDiskBytes(diskMb, prepared) {
4909
+ if (diskMb != null) {
4910
+ return diskMb * 1024 * 1024;
4911
+ }
4912
+ if (prepared.parent != null) {
4913
+ if (prepared.parent.rootfsDiskBytes == null) {
4914
+ throw new Error(
4915
+ "platform API did not return parent rootfsDiskBytes for diff build; pass diskMb explicitly or update Platform API"
4916
+ );
4917
+ }
4918
+ return prepared.parent.rootfsDiskBytes;
4919
+ }
4920
+ return DEFAULT_ROOTFS_DISK_MB * 1024 * 1024;
4921
+ }
4922
+ function rootfsDiskBytesToMb(bytes) {
4923
+ return Math.ceil(bytes / (1024 * 1024));
4924
+ }
4925
+ async function buildRootfsSpec(preparedSpec, prepared, plan, diskMb) {
4926
+ const spec = {
4927
+ ...preparedSpec,
4928
+ dockerfile: plan.dockerfileText,
4929
+ contextDir: REMOTE_CONTEXT_DIR,
4930
+ baseImage: plan.baseImage,
4931
+ rootfsDiskBytes: rootfsDiskBytes(diskMb, prepared)
4932
+ };
4933
+ const dockerConfigJson = await resolvedDockerConfigJson();
4934
+ if (dockerConfigJson != null) {
4935
+ spec.dockerConfigJson = dockerConfigJson;
4936
+ }
4937
+ return spec;
4938
+ }
4939
+ function rootfsBuilderExecutable(executable) {
4940
+ return executable === ROOTFS_BUILDER_COMMAND ? `${ROOTFS_BUILDER_BIN_DIR}/${ROOTFS_BUILDER_COMMAND}` : executable;
4941
+ }
4942
+ function rootfsBuilderEnv() {
4943
+ return { PATH: ROOTFS_BUILDER_PATH };
4944
+ }
4945
+ async function runRootfsBuilder(sandbox, command, emit, sleep3) {
4946
+ const parts = shellSplit(command);
4947
+ const [executable, ...commandArgs] = parts;
4948
+ if (!executable) {
4949
+ throw new Error("empty rootfs builder command returned by platform API");
4950
+ }
4951
+ await runStreaming(
4952
+ sandbox,
4953
+ emit,
4954
+ sleep3,
4955
+ rootfsBuilderExecutable(executable),
4956
+ [...commandArgs, "--spec", REMOTE_SPEC_PATH, "--metadata-out", REMOTE_METADATA_PATH],
4957
+ rootfsBuilderEnv(),
4958
+ REMOTE_BUILD_DIR
4959
+ );
4960
+ }
4961
+ function metadataString(metadata, snakeKey, camelKey) {
4962
+ const value = metadata[snakeKey] ?? metadata[camelKey];
4963
+ return typeof value === "string" ? value : void 0;
4964
+ }
4965
+ function metadataNumber(metadata, snakeKey, camelKey) {
4966
+ const value = metadata[snakeKey] ?? metadata[camelKey];
4967
+ if (typeof value === "number") {
4968
+ return value;
4969
+ }
4970
+ if (typeof value === "string" && value.trim()) {
4971
+ const parsed = Number(value);
4972
+ return Number.isFinite(parsed) ? parsed : void 0;
4973
+ }
4974
+ return void 0;
4975
+ }
4976
+ function completeRequestFromMetadata(prepared, metadata) {
4977
+ const rootfsNodeKind = metadataString(metadata, "rootfs_node_kind", "rootfsNodeKind") ?? prepared.rootfsNodeKind;
4978
+ const parentManifestUri = metadataString(metadata, "parent_manifest_uri", "parentManifestUri") ?? (rootfsNodeKind === "diff" ? prepared.parent?.parentManifestUri : void 0);
4979
+ if (rootfsNodeKind === "diff" && parentManifestUri == null) {
4980
+ throw new Error("rootfs diff build completed without parent_manifest_uri");
4981
+ }
4982
+ const snapshotFormatVersion = metadataString(
4983
+ metadata,
4984
+ "snapshot_format_version",
4985
+ "snapshotFormatVersion"
4986
+ );
4987
+ const snapshotSizeBytes = metadataNumber(
4988
+ metadata,
4989
+ "snapshot_size_bytes",
4990
+ "snapshotSizeBytes"
4991
+ );
4992
+ const rootfsDiskBytesValue = metadataNumber(
4993
+ metadata,
4994
+ "rootfs_disk_bytes",
4995
+ "rootfsDiskBytes"
4996
+ );
4997
+ if (!snapshotFormatVersion) {
4998
+ throw new Error("rootfs builder metadata is missing snapshot_format_version");
4999
+ }
5000
+ if (snapshotSizeBytes == null) {
5001
+ throw new Error("rootfs builder metadata is missing numeric snapshot_size_bytes");
5002
+ }
5003
+ if (rootfsDiskBytesValue == null) {
5004
+ throw new Error("rootfs builder metadata is missing numeric rootfs_disk_bytes");
5005
+ }
5006
+ return {
5007
+ snapshotId: metadataString(metadata, "snapshot_id", "snapshotId") ?? prepared.snapshotId,
5008
+ snapshotUri: metadataString(metadata, "snapshot_uri", "snapshotUri") ?? prepared.snapshotUri,
5009
+ snapshotFormatVersion,
5010
+ snapshotSizeBytes,
5011
+ rootfsDiskBytes: rootfsDiskBytesValue,
5012
+ rootfsNodeKind,
5013
+ ...parentManifestUri ? { parentManifestUri } : {}
5014
+ };
5015
+ }
5016
+ function requiredString(object, key) {
5017
+ const value = object[key];
5018
+ if (typeof value !== "string" || value.length === 0) {
5019
+ throw new Error(`expected '${key}' to be a non-empty string`);
5020
+ }
5021
+ return value;
5022
+ }
5023
+ function requiredNumber(object, key) {
5024
+ const value = object[key];
5025
+ if (typeof value !== "number" || !Number.isFinite(value)) {
5026
+ throw new Error(`expected '${key}' to be a finite number`);
5027
+ }
5028
+ return value;
5029
+ }
5030
+ function optionalNumber(object, key) {
5031
+ const value = object[key];
5032
+ if (value == null) {
5033
+ return void 0;
5034
+ }
5035
+ if (typeof value !== "number" || !Number.isFinite(value)) {
5036
+ throw new Error(`expected '${key}' to be a finite number`);
5037
+ }
5038
+ return value;
5039
+ }
4837
5040
  async function runChecked(sandbox, command, args, env, workingDir) {
4838
5041
  const result = await sandbox.run(command, {
4839
5042
  args,
@@ -4893,18 +5096,6 @@ function emitOutputLines(emit, stream, response, seen) {
4893
5096
  emit({ type: "build_log", stream, message: line });
4894
5097
  }
4895
5098
  }
4896
- function isPathWithinContext(contextDir, localPath) {
4897
- const relative = import_node_path.default.relative(contextDir, localPath);
4898
- return relative === "" || !relative.startsWith("..") && !import_node_path.default.isAbsolute(relative);
4899
- }
4900
- function resolveContextSourcePath(contextDir, source) {
4901
- const resolvedContextDir = import_node_path.default.resolve(contextDir);
4902
- const resolvedSource = import_node_path.default.resolve(resolvedContextDir, source);
4903
- if (!isPathWithinContext(resolvedContextDir, resolvedSource)) {
4904
- throw new Error(`Local path escapes the build context: ${source}`);
4905
- }
4906
- return resolvedSource;
4907
- }
4908
5099
  async function copyLocalPathToSandbox(sandbox, localPath, remotePath) {
4909
5100
  const fileStats = await (0, import_promises.stat)(localPath).catch(() => null);
4910
5101
  if (!fileStats) {
@@ -4935,168 +5126,7 @@ async function copyLocalPathToSandbox(sandbox, localPath, remotePath) {
4935
5126
  }
4936
5127
  }
4937
5128
  }
4938
- async function persistEnvVar(sandbox, processEnv, key, value) {
4939
- const exportLine = `export ${key}=${shellQuote(value)}`;
4940
- await runChecked(
4941
- sandbox,
4942
- "sh",
4943
- ["-c", `printf '%s\\n' ${shellQuote(exportLine)} >> /etc/environment`],
4944
- processEnv
4945
- );
4946
- }
4947
- async function copyFromContext(sandbox, emit, contextDir, sources, destination, workingDir, keyword) {
4948
- const destinationPath = resolveContainerPath(destination, workingDir);
4949
- if (sources.length > 1 && !destinationPath.endsWith("/")) {
4950
- throw new Error(
4951
- `${keyword} with multiple sources requires a directory destination ending in '/'`
4952
- );
4953
- }
4954
- for (const source of sources) {
4955
- const localSource = resolveContextSourcePath(contextDir, source);
4956
- const localStats = await (0, import_promises.stat)(localSource).catch(() => null);
4957
- if (!localStats) {
4958
- throw new Error(`Local path not found: ${localSource}`);
4959
- }
4960
- let remoteDestination = destinationPath;
4961
- if (sources.length > 1) {
4962
- remoteDestination = import_node_path.default.posix.join(
4963
- destinationPath.replace(/\/$/, ""),
4964
- import_node_path.default.posix.basename(source.replace(/\/$/, ""))
4965
- );
4966
- } else if (localStats.isFile() && destinationPath.endsWith("/")) {
4967
- remoteDestination = import_node_path.default.posix.join(
4968
- destinationPath.replace(/\/$/, ""),
4969
- import_node_path.default.basename(source)
4970
- );
4971
- }
4972
- emit({
4973
- type: "status",
4974
- message: `${keyword} ${source} -> ${remoteDestination}`
4975
- });
4976
- await copyLocalPathToSandbox(sandbox, localSource, remoteDestination);
4977
- }
4978
- }
4979
- async function addUrlToSandbox(sandbox, emit, url, destination, workingDir, processEnv, sleep3) {
4980
- let destinationPath = resolveContainerPath(destination, workingDir);
4981
- const parsedUrl = new URL(url);
4982
- const fileName = import_node_path.default.posix.basename(parsedUrl.pathname.replace(/\/$/, "")) || "downloaded";
4983
- if (destinationPath.endsWith("/")) {
4984
- destinationPath = import_node_path.default.posix.join(destinationPath.replace(/\/$/, ""), fileName);
4985
- }
4986
- const parentDir = import_node_path.default.posix.dirname(destinationPath) || "/";
4987
- emit({
4988
- type: "status",
4989
- message: `ADD ${url} -> ${destinationPath}`
4990
- });
4991
- await runChecked(sandbox, "mkdir", ["-p", parentDir], processEnv);
4992
- await runStreaming(
4993
- sandbox,
4994
- emit,
4995
- sleep3,
4996
- "sh",
4997
- [
4998
- "-c",
4999
- `curl -fsSL --location ${shellQuote(url)} -o ${shellQuote(destinationPath)}`
5000
- ],
5001
- processEnv,
5002
- workingDir
5003
- );
5004
- }
5005
- async function executeDockerfilePlan(sandbox, plan, emit, sleep3) {
5006
- const processEnv = { ...BUILD_SANDBOX_PIP_ENV };
5007
- let workingDir = "/";
5008
- for (const instruction of plan.instructions) {
5009
- const { keyword, value, lineNumber } = instruction;
5010
- if (keyword === "RUN") {
5011
- emit({ type: "status", message: `RUN ${value}` });
5012
- await runStreaming(
5013
- sandbox,
5014
- emit,
5015
- sleep3,
5016
- "sh",
5017
- ["-c", value],
5018
- processEnv,
5019
- workingDir
5020
- );
5021
- continue;
5022
- }
5023
- if (keyword === "WORKDIR") {
5024
- const tokens = shellSplit(value);
5025
- if (tokens.length !== 1) {
5026
- throw new Error(`line ${lineNumber}: WORKDIR must include exactly one path`);
5027
- }
5028
- workingDir = resolveContainerPath(tokens[0], workingDir);
5029
- emit({ type: "status", message: `WORKDIR ${workingDir}` });
5030
- await runChecked(sandbox, "mkdir", ["-p", workingDir], processEnv);
5031
- continue;
5032
- }
5033
- if (keyword === "ENV") {
5034
- for (const [key, envValue] of parseEnvPairs(value, lineNumber)) {
5035
- emit({ type: "status", message: `ENV ${key}=${envValue}` });
5036
- processEnv[key] = envValue;
5037
- await persistEnvVar(sandbox, processEnv, key, envValue);
5038
- }
5039
- continue;
5040
- }
5041
- if (keyword === "COPY") {
5042
- const { sources, destination } = parseCopyLikeValues(
5043
- value,
5044
- lineNumber,
5045
- keyword
5046
- );
5047
- await copyFromContext(
5048
- sandbox,
5049
- emit,
5050
- plan.contextDir,
5051
- sources,
5052
- destination,
5053
- workingDir,
5054
- keyword
5055
- );
5056
- continue;
5057
- }
5058
- if (keyword === "ADD") {
5059
- const { sources, destination } = parseCopyLikeValues(
5060
- value,
5061
- lineNumber,
5062
- keyword
5063
- );
5064
- if (sources.length === 1 && /^https?:\/\//.test(sources[0])) {
5065
- await addUrlToSandbox(
5066
- sandbox,
5067
- emit,
5068
- sources[0],
5069
- destination,
5070
- workingDir,
5071
- processEnv,
5072
- sleep3
5073
- );
5074
- } else {
5075
- await copyFromContext(
5076
- sandbox,
5077
- emit,
5078
- plan.contextDir,
5079
- sources,
5080
- destination,
5081
- workingDir,
5082
- keyword
5083
- );
5084
- }
5085
- continue;
5086
- }
5087
- if (IGNORED_DOCKERFILE_INSTRUCTIONS.has(keyword)) {
5088
- emit({
5089
- type: "warning",
5090
- message: `Skipping Dockerfile instruction '${keyword}' during snapshot materialization. It is still preserved in the registered Dockerfile.`
5091
- });
5092
- continue;
5093
- }
5094
- throw new Error(
5095
- `line ${lineNumber}: Dockerfile instruction '${keyword}' is not supported for sandbox image creation`
5096
- );
5097
- }
5098
- }
5099
- async function registerImage(context, name, dockerfile, snapshotId, snapshotSandboxId, snapshotUri, snapshotSizeBytes, rootfsDiskBytes, isPublic, snapshotFormatVersion) {
5129
+ async function registerImage(context, name, dockerfile, snapshotId, snapshotSandboxId, snapshotUri, snapshotSizeBytes, rootfsDiskBytes2, isPublic, snapshotFormatVersion) {
5100
5130
  const bearerToken = context.apiKey ?? context.personalAccessToken;
5101
5131
  if (!bearerToken) {
5102
5132
  throw new Error("Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT.");
@@ -5130,7 +5160,7 @@ async function registerImage(context, name, dockerfile, snapshotId, snapshotSand
5130
5160
  snapshotUri,
5131
5161
  ...snapshotFormatVersion ? { snapshotFormatVersion } : {},
5132
5162
  snapshotSizeBytes,
5133
- rootfsDiskBytes,
5163
+ rootfsDiskBytes: rootfsDiskBytes2,
5134
5164
  public: isPublic
5135
5165
  })
5136
5166
  });
@@ -5147,67 +5177,71 @@ async function createSandboxImage(source, options = {}, deps = {}) {
5147
5177
  const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
5148
5178
  const context = buildContextFromEnv();
5149
5179
  const clientFactory = deps.createClient ?? createDefaultClient;
5150
- const register = deps.registerImage ?? ((...args) => registerImage(...args));
5151
5180
  const sourceLabel = typeof source === "string" ? source : `Image(${source.name})`;
5152
5181
  emit({ type: "status", message: `Loading ${sourceLabel}...` });
5153
5182
  const plan = typeof source === "string" ? await loadDockerfilePlan(source, options.registeredName) : loadImagePlan(source, options);
5154
5183
  emit({
5155
5184
  type: "status",
5156
- message: plan.baseImage == null ? "Starting build sandbox with the default server image..." : `Starting build sandbox from ${plan.baseImage}...`
5185
+ message: `Selected image name: ${plan.registeredName}`
5186
+ });
5187
+ emit({ type: "status", message: "Preparing rootfs build..." });
5188
+ const resolvedContext = await resolveBuildContext(context);
5189
+ const { prepared, spec: preparedSpec } = await prepareRootfsBuild(
5190
+ resolvedContext,
5191
+ plan,
5192
+ options.isPublic ?? false
5193
+ );
5194
+ emit({
5195
+ type: "status",
5196
+ message: prepared.rootfsNodeKind === "diff" ? "Build mode: RootfsDiff" : "Build mode: RootfsBase"
5157
5197
  });
5158
5198
  const client = clientFactory(context);
5159
5199
  let sandbox;
5160
5200
  try {
5161
- sandbox = await client.createAndConnect({
5162
- ...plan.baseImage == null ? {} : { image: plan.baseImage },
5163
- cpus: options.cpus ?? 2,
5164
- memoryMb: options.memoryMb ?? 4096,
5165
- ...options.diskMb != null ? { diskMb: options.diskMb } : {}
5166
- });
5201
+ const outputRootfsDiskBytes = rootfsDiskBytes(options.diskMb, prepared);
5202
+ const builderDiskMb = Math.max(
5203
+ rootfsDiskBytesToMb(outputRootfsDiskBytes),
5204
+ options.builderDiskMb ?? prepared.builder.diskMb
5205
+ );
5167
5206
  emit({
5168
5207
  type: "status",
5169
- message: `Materializing image in sandbox ${sandbox.sandboxId}...`
5208
+ message: `Creating rootfs builder sandbox from ${prepared.builder.image}...`
5170
5209
  });
5171
- await executeDockerfilePlan(sandbox, plan, emit, sleep3);
5172
- emit({ type: "status", message: "Creating snapshot..." });
5173
- const snapshot = await client.snapshotAndWait(sandbox.sandboxId, {
5174
- snapshotType: "filesystem",
5175
- waitUntil: "completed"
5210
+ sandbox = await client.createAndConnect({
5211
+ image: prepared.builder.image,
5212
+ cpus: options.cpus ?? prepared.builder.cpus,
5213
+ memoryMb: options.memoryMb ?? prepared.builder.memoryMb,
5214
+ diskMb: builderDiskMb
5176
5215
  });
5177
- emit({
5178
- type: "snapshot_created",
5179
- snapshot_id: snapshot.snapshotId
5180
- });
5181
- if (!snapshot.snapshotUri) {
5182
- throw new Error(
5183
- `Snapshot ${snapshot.snapshotId} is missing snapshotUri and cannot be registered as a sandbox image.`
5184
- );
5185
- }
5186
- if (snapshot.sizeBytes == null) {
5187
- throw new Error(
5188
- `Snapshot ${snapshot.snapshotId} is missing sizeBytes and cannot be registered as a sandbox image.`
5189
- );
5190
- }
5191
- if (snapshot.rootfsDiskBytes == null) {
5192
- throw new Error(
5193
- `Snapshot ${snapshot.snapshotId} is missing rootfsDiskBytes and cannot be registered as a sandbox image.`
5194
- );
5195
- }
5196
5216
  emit({
5197
5217
  type: "status",
5198
- message: `Registering image '${plan.registeredName}'...`
5218
+ message: `Rootfs builder sandbox ${sandbox.sandboxId} is running`
5199
5219
  });
5200
- const result = await register(
5201
- context,
5202
- plan.registeredName,
5203
- plan.dockerfileText,
5204
- snapshot.snapshotId,
5205
- snapshot.sandboxId,
5206
- snapshot.snapshotUri,
5207
- snapshot.sizeBytes,
5208
- snapshot.rootfsDiskBytes,
5209
- options.isPublic ?? false,
5210
- snapshot.snapshotFormatVersion
5220
+ emit({ type: "status", message: "Uploading build context..." });
5221
+ await copyLocalPathToSandbox(sandbox, plan.contextDir, REMOTE_CONTEXT_DIR);
5222
+ const spec = await buildRootfsSpec(
5223
+ preparedSpec,
5224
+ prepared,
5225
+ plan,
5226
+ options.diskMb
5227
+ );
5228
+ await runChecked(sandbox, "mkdir", ["-p", import_node_path.default.posix.dirname(REMOTE_SPEC_PATH)]);
5229
+ await sandbox.writeFile(
5230
+ REMOTE_SPEC_PATH,
5231
+ new TextEncoder().encode(JSON.stringify(spec, null, 2))
5232
+ );
5233
+ emit({ type: "status", message: "Running offline rootfs builder..." });
5234
+ await runRootfsBuilder(sandbox, prepared.builder.command, emit, sleep3);
5235
+ const metadataBytes = await sandbox.readFile(REMOTE_METADATA_PATH);
5236
+ const metadata = JSON.parse(
5237
+ new TextDecoder().decode(metadataBytes)
5238
+ );
5239
+ const completeRequest = completeRequestFromMetadata(prepared, metadata);
5240
+ emit({ type: "status", message: "Completing image registration..." });
5241
+ const result = await completeRootfsBuild(
5242
+ resolvedContext,
5243
+ prepared.buildId,
5244
+ completeRequest
5211
5245
  );
5212
5246
  emit({
5213
5247
  type: "image_registered",
@@ -5235,16 +5269,18 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
5235
5269
  cpus: { type: "string" },
5236
5270
  memory: { type: "string" },
5237
5271
  disk_mb: { type: "string" },
5272
+ builder_disk_mb: { type: "string" },
5238
5273
  public: { type: "boolean", default: false }
5239
5274
  }
5240
5275
  });
5241
5276
  const dockerfilePath = parsed.positionals[0];
5242
5277
  if (!dockerfilePath) {
5243
- throw new Error("Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--disk_mb MB] [--public]");
5278
+ throw new Error("Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--disk_mb MB] [--builder_disk_mb MB] [--public]");
5244
5279
  }
5245
5280
  const cpus = parsed.values.cpus != null ? Number(parsed.values.cpus) : void 0;
5246
5281
  const memoryMb = parsed.values.memory != null ? Number(parsed.values.memory) : void 0;
5247
5282
  const diskMb = parsed.values.disk_mb != null ? Number(parsed.values.disk_mb) : void 0;
5283
+ const builderDiskMb = parsed.values.builder_disk_mb != null ? Number(parsed.values.builder_disk_mb) : void 0;
5248
5284
  if (cpus != null && !Number.isFinite(cpus)) {
5249
5285
  throw new Error(`Invalid --cpus value: ${parsed.values.cpus}`);
5250
5286
  }
@@ -5254,6 +5290,11 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
5254
5290
  if (diskMb != null && !Number.isInteger(diskMb)) {
5255
5291
  throw new Error(`Invalid --disk_mb value: ${parsed.values.disk_mb}`);
5256
5292
  }
5293
+ if (builderDiskMb != null && !Number.isInteger(builderDiskMb)) {
5294
+ throw new Error(
5295
+ `Invalid --builder_disk_mb value: ${parsed.values.builder_disk_mb}`
5296
+ );
5297
+ }
5257
5298
  await createSandboxImage(
5258
5299
  dockerfilePath,
5259
5300
  {
@@ -5261,31 +5302,31 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
5261
5302
  cpus,
5262
5303
  memoryMb,
5263
5304
  diskMb,
5305
+ builderDiskMb,
5264
5306
  isPublic: parsed.values.public
5265
5307
  },
5266
5308
  { emit: ndjsonStdoutEmit }
5267
5309
  );
5268
5310
  }
5269
- var import_promises, import_node_path, import_node_util, BUILD_SANDBOX_PIP_ENV, IGNORED_DOCKERFILE_INSTRUCTIONS, UNSUPPORTED_DOCKERFILE_INSTRUCTIONS;
5311
+ var import_promises, import_node_os, import_node_path, import_node_util, 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, UNSUPPORTED_DOCKERFILE_INSTRUCTIONS;
5270
5312
  var init_sandbox_image = __esm({
5271
5313
  "src/sandbox-image.ts"() {
5272
5314
  "use strict";
5273
5315
  import_promises = require("fs/promises");
5316
+ import_node_os = require("os");
5274
5317
  import_node_path = __toESM(require("path"), 1);
5275
5318
  import_node_util = require("util");
5276
5319
  init_models();
5277
5320
  init_client();
5278
5321
  init_image();
5279
- BUILD_SANDBOX_PIP_ENV = { PIP_BREAK_SYSTEM_PACKAGES: "1" };
5280
- IGNORED_DOCKERFILE_INSTRUCTIONS = /* @__PURE__ */ new Set([
5281
- "CMD",
5282
- "ENTRYPOINT",
5283
- "EXPOSE",
5284
- "HEALTHCHECK",
5285
- "LABEL",
5286
- "STOPSIGNAL",
5287
- "VOLUME"
5288
- ]);
5322
+ DEFAULT_ROOTFS_DISK_MB = 10 * 1024;
5323
+ REMOTE_BUILD_DIR = "/var/lib/tensorlake/rootfs-builder/build";
5324
+ REMOTE_CONTEXT_DIR = "/var/lib/tensorlake/rootfs-builder/build/context";
5325
+ REMOTE_SPEC_PATH = "/var/lib/tensorlake/rootfs-builder/build/spec.json";
5326
+ REMOTE_METADATA_PATH = "/var/lib/tensorlake/rootfs-builder/build/metadata.json";
5327
+ ROOTFS_BUILDER_BIN_DIR = "/usr/local/bin";
5328
+ ROOTFS_BUILDER_PATH = "/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
5329
+ ROOTFS_BUILDER_COMMAND = "tl-rootfs-build";
5289
5330
  UNSUPPORTED_DOCKERFILE_INSTRUCTIONS = /* @__PURE__ */ new Set([
5290
5331
  "ARG",
5291
5332
  "ONBUILD",