trainfabric 0.1.45 → 0.1.47

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.
Files changed (2) hide show
  1. package/dist/index.cjs +93 -13
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -7648,6 +7648,7 @@ var runCreateSchema = external_exports.object({
7648
7648
  runtimeManifestId: external_exports.string().min(1).optional(),
7649
7649
  profileSessionId: external_exports.string().min(1).optional(),
7650
7650
  pricingQuoteId: external_exports.string().min(1).optional(),
7651
+ maxCostUsd: external_exports.number().positive().max(1e5).optional(),
7651
7652
  experimentTracking: experimentTrackingInputSchema.optional()
7652
7653
  });
7653
7654
  var jobProfileSchema = runCreateSchema;
@@ -7693,6 +7694,7 @@ var runQuoteSchema = external_exports.object({
7693
7694
  hyperparameters: runHyperparametersSchema.optional(),
7694
7695
  runtimeVersion: external_exports.string().min(1).optional(),
7695
7696
  mode: external_exports.enum(trainingModes).optional(),
7697
+ maxCostUsd: external_exports.number().positive().max(1e5).optional(),
7696
7698
  reservationClass: external_exports.enum(reservationClasses).optional(),
7697
7699
  slaClass: external_exports.enum(slaClasses).optional(),
7698
7700
  runtimeManifestId: external_exports.string().min(1).optional(),
@@ -7762,11 +7764,20 @@ var serviceAccountCreateSchema = external_exports.object({
7762
7764
  scopes: external_exports.array(external_exports.enum(apiKeyScopes)).min(1)
7763
7765
  });
7764
7766
  var billingCheckoutSessionCreateSchema = external_exports.object({
7765
- amountUsd: external_exports.number().positive().max(1e5).optional(),
7767
+ amountUsd: external_exports.number().min(5).max(1e5).optional(),
7766
7768
  mode: external_exports.enum(["subscription", "credits"]).default("subscription"),
7767
7769
  planId: external_exports.enum(["hobby", "pro"]).optional(),
7768
7770
  priceId: external_exports.string().min(1).optional()
7769
7771
  });
7772
+ var couponRedeemSchema = external_exports.object({
7773
+ code: external_exports.string().trim().min(1).max(64)
7774
+ });
7775
+ var creditAdjustmentCreateSchema = external_exports.object({
7776
+ amountUsd: external_exports.number().min(-1e5).max(1e5).refine((value) => value !== 0, "Credit adjustment amount cannot be zero."),
7777
+ kind: external_exports.enum(["manual_adjustment", "refund_credit", "refund_debit"]),
7778
+ reason: external_exports.string().trim().min(1).max(500),
7779
+ sourceId: external_exports.string().trim().min(1).max(128).optional()
7780
+ });
7770
7781
  var treasuryCounterpartyCreateSchema = external_exports.object({
7771
7782
  providerId: external_exports.enum(treasuryProviderIds).default("mercury"),
7772
7783
  name: external_exports.string().min(1),
@@ -8613,7 +8624,27 @@ function encodeRunId(runId) {
8613
8624
  }
8614
8625
  return encodeURIComponent(id);
8615
8626
  }
8627
+ function isTransientQuoteError(error) {
8628
+ const message = error instanceof Error ? error.message : String(error);
8629
+ return /status (502|503|504|429)\b|fetch failed|network|ECONNRESET|ETIMEDOUT/i.test(message);
8630
+ }
8616
8631
  var RunsClient = class extends ResourceClient {
8632
+ async retryQuoteOperation(operation) {
8633
+ const attempts = Math.max(1, Number(process.env.TRAINFABRIC_QUOTE_RETRY_ATTEMPTS ?? 5));
8634
+ let lastError;
8635
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
8636
+ try {
8637
+ return await operation();
8638
+ } catch (error) {
8639
+ lastError = error;
8640
+ if (!isTransientQuoteError(error) || attempt === attempts) {
8641
+ throw error;
8642
+ }
8643
+ await wait(Math.min(5e3, 400 * 2 ** (attempt - 1)));
8644
+ }
8645
+ }
8646
+ throw lastError instanceof Error ? lastError : new Error("Quote request failed.");
8647
+ }
8617
8648
  describeEmptyQuoteFailure(input) {
8618
8649
  const compute = input.compute && input.compute !== "auto" && input.compute !== "cpu-sim" ? input.compute : void 0;
8619
8650
  const requestedAccelerator = compute?.acceleratorClass;
@@ -8628,7 +8659,15 @@ var RunsClient = class extends ResourceClient {
8628
8659
  if (constraints.length === 0) {
8629
8660
  return "No quote options were returned for the requested run.";
8630
8661
  }
8631
- return `No quote options were returned for the requested run under the current hardware constraints (${constraints.join(", ")}). Omit the accelerator constraint or request a larger GPU memory floor.`;
8662
+ const suggestions = [];
8663
+ if (requestedAccelerator) {
8664
+ suggestions.push("omit the accelerator constraint");
8665
+ }
8666
+ if (requestedMemory) {
8667
+ suggestions.push("lower --min-memory if it excludes available GPUs");
8668
+ }
8669
+ suggestions.push("try a different mode or retry when capacity changes");
8670
+ return `No quote options were returned for the requested run under the current hardware constraints (${constraints.join(", ")}). ${suggestions.join(", ")}.`;
8632
8671
  }
8633
8672
  profile(input) {
8634
8673
  const payload = jobProfileSchema.parse({
@@ -8664,26 +8703,29 @@ var RunsClient = class extends ResourceClient {
8664
8703
  if (profileSessionId) {
8665
8704
  return this.waitForProfile(profileSessionId);
8666
8705
  }
8667
- const session = await this.profile(input);
8668
- return this.waitForProfile(session.id);
8706
+ const session = await this.retryQuoteOperation(() => this.profile(input));
8707
+ return this.retryQuoteOperation(() => this.waitForProfile(session.id));
8669
8708
  }
8670
8709
  async quote(input) {
8671
- const detail = await this.ensureReadyProfile(input);
8710
+ const detail = await this.retryQuoteOperation(() => this.ensureReadyProfile(input));
8672
8711
  const payload = runQuotesSchema.parse({
8673
8712
  ...input,
8674
8713
  projectId: input.projectId ?? this.parent.projectId,
8675
8714
  profileSessionId: detail.session.id
8676
8715
  });
8677
- return this.requestPost("/v1/runs/quotes", payload);
8716
+ return this.requestQuoteWithRetry("/v1/runs/quotes", payload);
8678
8717
  }
8679
8718
  async quoteLegacy(input) {
8680
- const detail = await this.ensureReadyProfile(input);
8719
+ const detail = await this.retryQuoteOperation(() => this.ensureReadyProfile(input));
8681
8720
  const payload = runQuoteSchema.parse({
8682
8721
  ...input,
8683
8722
  projectId: input.projectId ?? this.parent.projectId,
8684
8723
  profileSessionId: detail.session.id
8685
8724
  });
8686
- return this.requestPost("/v1/runs/quote", payload);
8725
+ return this.requestQuoteWithRetry("/v1/runs/quote", payload);
8726
+ }
8727
+ async requestQuoteWithRetry(pathname, payload) {
8728
+ return this.retryQuoteOperation(() => this.requestPost(pathname, payload));
8687
8729
  }
8688
8730
  async create(input) {
8689
8731
  const projectId = input.projectId ?? this.parent.projectId;
@@ -9269,6 +9311,16 @@ function normalizeLearningRate(lr) {
9269
9311
  }
9270
9312
  return parsed;
9271
9313
  }
9314
+ function normalizeMaxCost(maxCost) {
9315
+ if (maxCost === void 0) {
9316
+ return void 0;
9317
+ }
9318
+ const parsed = Number(maxCost);
9319
+ if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 1e5) {
9320
+ throw new Error("Max cost must be a positive USD amount up to 100000.");
9321
+ }
9322
+ return Number(parsed.toFixed(2));
9323
+ }
9272
9324
  function buildComputeSpec(options) {
9273
9325
  const gpuCount = normalizePositiveInteger(options.gpus, "GPU count", 1, 64);
9274
9326
  const nodeCount = normalizePositiveInteger(options.nodes, "Node count", 1, 16);
@@ -9310,7 +9362,7 @@ function buildComputeSpec(options) {
9310
9362
 
9311
9363
  // src/index.ts
9312
9364
  var DEFAULT_TRAINFABRIC_API_URL2 = "https://api.trainfabric.com";
9313
- var CLI_VERSION = "0.1.45";
9365
+ var CLI_VERSION = "0.1.47";
9314
9366
  var CONFIG_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".trainfabric");
9315
9367
  var CONFIG_PATH = import_node_path3.default.join(CONFIG_DIR, "config.json");
9316
9368
  var FALLBACK_SECRET_PATH = import_node_path3.default.join(CONFIG_DIR, "session.enc");
@@ -9749,6 +9801,7 @@ function buildRunInput(options, config = loadConfig()) {
9749
9801
  pricingQuoteId: quoteId,
9750
9802
  evalDatasetId: normalizeOptionalId(options.eval, "dataset", "Eval dataset ID"),
9751
9803
  ...sourceOptions,
9804
+ maxCostUsd: normalizeMaxCost(options.maxCost),
9752
9805
  mode: normalizeTrainingMode(options.mode)
9753
9806
  };
9754
9807
  if (projectId) {
@@ -9787,10 +9840,18 @@ function assertQuoteOptions(bundle, options) {
9787
9840
  if (constraints.length === 0) {
9788
9841
  throw new Error("No quote options were returned for the requested run.");
9789
9842
  }
9843
+ const suggestions = [];
9844
+ if (options.accelerator) {
9845
+ suggestions.push("omit --accelerator");
9846
+ }
9847
+ if (options.minMemory) {
9848
+ suggestions.push("adjust --min-memory if it excludes available GPUs");
9849
+ }
9850
+ suggestions.push("try a different --mode or retry when capacity changes");
9790
9851
  throw new Error(
9791
9852
  `No quote options were returned for the requested run under the current hardware constraints (${constraints.join(
9792
9853
  ", "
9793
- )}). Try a different --mode, omit --accelerator, adjust --min-memory if it excludes available GPUs, or retry when capacity changes.`
9854
+ )}). ${suggestions.join(", ")}.`
9794
9855
  );
9795
9856
  }
9796
9857
  function filterQuoteBundleForRequestedMode(bundle, options) {
@@ -9843,7 +9904,8 @@ async function watchRun(runId, json = false, timeoutMs, pollMs) {
9843
9904
  });
9844
9905
  handle.on("log", (event) => {
9845
9906
  const payload = event.payload;
9846
- console.log(`[${payload.phase ?? "run"}:${payload.level ?? "info"}] ${payload.message ?? ""}`);
9907
+ const eventLabel = formatRunEventCode(payload.eventCode);
9908
+ console.log(`[${eventLabel ?? payload.phase ?? "run"}:${payload.level ?? "info"}] ${payload.message ?? ""}`);
9847
9909
  });
9848
9910
  handle.on("metric", (event) => {
9849
9911
  const payload = event.payload;
@@ -9874,6 +9936,24 @@ async function watchRun(runId, json = false, timeoutMs, pollMs) {
9874
9936
  );
9875
9937
  }
9876
9938
  }
9939
+ function formatRunEventCode(eventCode) {
9940
+ switch (eventCode) {
9941
+ case "provider_waiting_for_ssh":
9942
+ return "provider-waiting";
9943
+ case "relay_confirmation_timeout_nonfatal":
9944
+ return "relay-monitoring";
9945
+ case "dependency_installing":
9946
+ return "dependency-installing";
9947
+ case "runtime_bootstrap_fallback":
9948
+ return "runtime-bootstrap";
9949
+ case "training_started":
9950
+ return "training-started";
9951
+ case "artifact_syncing":
9952
+ return "artifact-syncing";
9953
+ default:
9954
+ return void 0;
9955
+ }
9956
+ }
9877
9957
  function parseDurationMs(value) {
9878
9958
  if (!value) {
9879
9959
  return void 0;
@@ -10082,7 +10162,7 @@ program2.command("datasets:upload").argument("<file>").option("--project <projec
10082
10162
  });
10083
10163
  printJson(dataset);
10084
10164
  });
10085
- program2.command("runs:create").option("--project <projectId>").option("--dataset <datasetId>").option("--model <baseModel>").option("--eval <evalDatasetId>").option("--epochs <epochs>", "number of epochs", "3").option("--lr <lr>", "learning rate", "0.0002").option("--gpus <gpuCount>", "gpu count", "1").option("--nodes <nodeCount>", "node count", "1").option("--accelerator <acceleratorClass>", "optional hard accelerator constraint (for example: a10g, a100, h100)").option("--min-memory <gigabytes>", "minimum GPU memory in GB").option("--precision <precision>", "fp16_bf16, fp8, or fp32").option("--interconnect <interconnect>", "pcie or nvlink").option("--mode <mode>", "efficient, balanced, or power").option("--quote <pricingQuoteId>", "launch the exact accepted quote id from runs:quote; use only with --yes").option("--repo <path>", "local repo path for runtime autodetect").option("--git <url>", "git repo URL for runtime metadata").option("--branch <branch>", "git branch for runtime metadata").option("--yes", "confirm that you reviewed pricing with runs:quote --summary and accept fluctuating realized usage").description("Create a training run. When using --quote, do not pass project, dataset, model, compute, mode, source, or hyperparameter flags; launch with `trainfabric runs:create --quote <quoteId> --yes`.").action(async (options, command) => {
10165
+ program2.command("runs:create").option("--project <projectId>").option("--dataset <datasetId>").option("--model <baseModel>").option("--eval <evalDatasetId>").option("--epochs <epochs>", "number of epochs", "3").option("--lr <lr>", "learning rate", "0.0002").option("--gpus <gpuCount>", "gpu count", "1").option("--nodes <nodeCount>", "node count", "1").option("--accelerator <acceleratorClass>", "optional hard accelerator constraint (for example: a10g, a100, h100)").option("--min-memory <gigabytes>", "minimum GPU memory in GB").option("--precision <precision>", "fp16_bf16, fp8, or fp32").option("--interconnect <interconnect>", "pcie or nvlink").option("--mode <mode>", "efficient, balanced, or power").option("--max-cost <usd>", "maximum accepted quoted customer charge in USD").option("--quote <pricingQuoteId>", "launch the exact accepted quote id from runs:quote; use only with --yes").option("--repo <path>", "local repo path for runtime autodetect").option("--git <url>", "git repo URL for runtime metadata").option("--branch <branch>", "git branch for runtime metadata").option("--yes", "confirm that you reviewed pricing with runs:quote --summary and accept fluctuating realized usage").description("Create a training run. When using --quote, do not pass project, dataset, model, compute, mode, source, or hyperparameter flags; launch with `trainfabric runs:create --quote <quoteId> --yes`.").action(async (options, command) => {
10086
10166
  const config = loadConfig();
10087
10167
  assertQuoteCreateHasNoLaunchOverrides(options, command);
10088
10168
  const runInput = buildRunInput(options, config);
@@ -10129,7 +10209,7 @@ program2.command("runtime:build").option("--project <projectId>").requiredOption
10129
10209
  )
10130
10210
  );
10131
10211
  });
10132
- program2.command("runs:quote").option("--project <projectId>").requiredOption("--dataset <datasetId>").requiredOption("--model <baseModel>").option("--eval <evalDatasetId>").option("--gpus <gpuCount>", "gpu count", "1").option("--nodes <nodeCount>", "node count", "1").option("--accelerator <acceleratorClass>", "optional hard accelerator constraint (for example: a10g, a100, h100)").option("--min-memory <gigabytes>", "minimum GPU memory in GB").option("--precision <precision>", "fp16_bf16, fp8, or fp32").option("--interconnect <interconnect>", "pcie or nvlink").option("--mode <mode>", "efficient, balanced, or power").option("--repo <path>").option("--git <url>").option("--branch <branch>").option("--json", "print raw JSON output; this is the default unless --summary is used").option("--summary", "print a human-readable price summary instead of raw JSON").description("Preview Efficient, Balanced, and Power launch options").action(async (options) => {
10212
+ program2.command("runs:quote").option("--project <projectId>").requiredOption("--dataset <datasetId>").requiredOption("--model <baseModel>").option("--eval <evalDatasetId>").option("--gpus <gpuCount>", "gpu count", "1").option("--nodes <nodeCount>", "node count", "1").option("--accelerator <acceleratorClass>", "optional hard accelerator constraint (for example: a10g, a100, h100)").option("--min-memory <gigabytes>", "minimum GPU memory in GB").option("--precision <precision>", "fp16_bf16, fp8, or fp32").option("--interconnect <interconnect>", "pcie or nvlink").option("--mode <mode>", "efficient, balanced, or power").option("--max-cost <usd>", "maximum accepted quoted customer charge in USD").option("--repo <path>").option("--git <url>").option("--branch <branch>").option("--json", "print raw JSON output; this is the default unless --summary is used").option("--summary", "print a human-readable price summary instead of raw JSON").description("Preview Efficient, Balanced, and Power launch options").action(async (options) => {
10133
10213
  const config = loadConfig();
10134
10214
  const bundle = filterQuoteBundleForRequestedMode(
10135
10215
  await createClient(config).runs.quote(buildRunInput(options, config)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trainfabric",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "Trainfabric CLI for launching GPU training jobs on the hosted Trainfabric backend.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",