@camstack/system 1.0.8 → 1.1.1

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.
@@ -300,6 +300,42 @@ function createFileDataPlaneHandler(opts) {
300
300
  }
301
301
  //#endregion
302
302
  //#region src/download/model-downloader.ts
303
+ function isNonEmptyFile(filePath) {
304
+ return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
305
+ }
306
+ /**
307
+ * Sibling files of a single-file (non-directory) format — extra files the
308
+ * format needs, fetched from the same remote directory as `url` and stored
309
+ * flat alongside the main file in `modelsDir`. Catalog-declared via
310
+ * `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
311
+ * `.xml`. The downloader stays format-agnostic; the convention lives in the
312
+ * catalog data (like `MLPACKAGE_FILES` for the directory case).
313
+ */
314
+ function siblingFilesFor(formatEntry) {
315
+ return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
316
+ }
317
+ /** Resolve a sibling's remote URL relative to the main file's directory. */
318
+ function siblingUrl(mainUrl, sibling) {
319
+ return mainUrl.replace(/[^/]+$/, sibling);
320
+ }
321
+ /**
322
+ * The relative filenames a model format occupies under `modelsDir` — the main
323
+ * file (URL basename, or `${id}.${format}` fallback), its declared siblings
324
+ * (e.g. the OpenVINO `.bin` next to the `.xml`), and any `extraFiles`. For a
325
+ * directory bundle the single entry is the bundle dir itself (tar/extract
326
+ * recurse into it).
327
+ *
328
+ * Used by model distribution (P2): the hub tars exactly these names from its
329
+ * `modelsDir` and the agent untars them into its own `modelsDir`, so both ends
330
+ * agree on the on-disk layout without re-deriving it.
331
+ */
332
+ function collectModelFiles(entry, format) {
333
+ const formatEntry = entry.formats[format];
334
+ if (!formatEntry) return [];
335
+ const names = [formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`, ...siblingFilesFor(formatEntry)];
336
+ for (const extra of entry.extraFiles ?? []) names.push(extra.filename);
337
+ return names;
338
+ }
303
339
  /** Build fetch headers, including HF auth token for huggingface.co URLs */
304
340
  function buildHeaders(url) {
305
341
  const headers = { "User-Agent": "CamStack/1.0" };
@@ -465,14 +501,18 @@ async function ensureModel(modelsDir, entry, format, onProgress) {
465
501
  if (entry.extraFiles) for (const extra of entry.extraFiles) await downloadFile(extra.url, path$1.join(modelsDir, extra.filename));
466
502
  const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
467
503
  const modelPath = path$1.join(modelsDir, filename);
504
+ const siblings = siblingFilesFor(formatEntry);
468
505
  if (fs.existsSync(modelPath)) if (formatEntry.isDirectory && !fs.existsSync(path$1.join(modelPath, "Manifest.json"))) fs.rmSync(modelPath, {
469
506
  recursive: true,
470
507
  force: true
471
508
  });
472
- else return modelPath;
509
+ else if (siblings.some((f) => !isNonEmptyFile(path$1.join(modelsDir, f)))) {} else return modelPath;
473
510
  fs.mkdirSync(modelsDir, { recursive: true });
474
511
  if (formatEntry.isDirectory) await downloadDirectory(formatEntry.url, modelPath, formatEntry.files, onProgress);
475
- else await downloadFile(formatEntry.url, modelPath, (downloaded, total) => onProgress?.(downloaded, total === 0 ? void 0 : total));
512
+ else {
513
+ await downloadFile(formatEntry.url, modelPath, (downloaded, total) => onProgress?.(downloaded, total === 0 ? void 0 : total));
514
+ for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), path$1.join(modelsDir, sibling));
515
+ }
476
516
  return modelPath;
477
517
  }
478
518
  /** Compute the on-disk path for a given model + format, even when not yet downloaded. */
@@ -489,17 +529,25 @@ function isModelDownloaded(modelsDir, entry, format) {
489
529
  const modelPath = getModelFilePath(modelsDir, entry, format);
490
530
  if (!modelPath || !fs.existsSync(modelPath)) return false;
491
531
  if (formatEntry.isDirectory) return fs.existsSync(path$1.join(modelPath, "Manifest.json"));
492
- return fs.statSync(modelPath).size > 0;
532
+ if (fs.statSync(modelPath).size <= 0) return false;
533
+ return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(path$1.join(modelsDir, f)));
493
534
  }
494
535
  /** Remove the on-disk model file/directory. Returns true if something was deleted. */
495
536
  function deleteModelFromDisk(modelsDir, entry, format) {
496
537
  const modelPath = getModelFilePath(modelsDir, entry, format);
497
538
  if (!modelPath || !fs.existsSync(modelPath)) return false;
498
- if (entry.formats[format]?.isDirectory) fs.rmSync(modelPath, {
539
+ const formatEntry = entry.formats[format];
540
+ if (formatEntry?.isDirectory) fs.rmSync(modelPath, {
499
541
  recursive: true,
500
542
  force: true
501
543
  });
502
- else fs.unlinkSync(modelPath);
544
+ else {
545
+ fs.unlinkSync(modelPath);
546
+ if (formatEntry) for (const sibling of siblingFilesFor(formatEntry)) {
547
+ const sibPath = path$1.join(modelsDir, sibling);
548
+ if (fs.existsSync(sibPath)) fs.unlinkSync(sibPath);
549
+ }
550
+ }
503
551
  return true;
504
552
  }
505
553
  //#endregion
@@ -536,15 +584,21 @@ var ModelDownloadService = class {
536
584
  if (!formatEntry) throw new Error(`ModelDownloadService: model "${modelId}" has no ${selectedFormat} format`);
537
585
  await this.ensureExtraFiles(modelId);
538
586
  const modelPath = this.modelFilePath(entry, selectedFormat);
539
- if (fs.existsSync(modelPath)) if (formatEntry.isDirectory) if (!fs.existsSync(path$1.join(modelPath, "Manifest.json"))) fs.rmSync(modelPath, {
540
- recursive: true,
541
- force: true
542
- });
543
- else return modelPath;
544
- else return modelPath;
587
+ const siblings = siblingFilesFor(formatEntry);
588
+ if (fs.existsSync(modelPath)) {
589
+ if (formatEntry.isDirectory) if (!fs.existsSync(path$1.join(modelPath, "Manifest.json"))) fs.rmSync(modelPath, {
590
+ recursive: true,
591
+ force: true
592
+ });
593
+ else return modelPath;
594
+ else if (siblings.every((f) => isNonEmptyFile(path$1.join(this.modelsDir, f)))) return modelPath;
595
+ }
545
596
  fs.mkdirSync(this.modelsDir, { recursive: true });
546
597
  if (formatEntry.isDirectory) await this.downloadDirectory(formatEntry.url, modelPath, formatEntry.files, modelId);
547
- else await downloadFile(formatEntry.url, modelPath, this.onProgress ? (downloaded, total) => this.onProgress(modelId, downloaded, total) : void 0);
598
+ else {
599
+ await downloadFile(formatEntry.url, modelPath, this.onProgress ? (downloaded, total) => this.onProgress(modelId, downloaded, total) : void 0);
600
+ for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), path$1.join(this.modelsDir, sibling));
601
+ }
548
602
  return modelPath;
549
603
  }
550
604
  /**
@@ -578,7 +632,8 @@ var ModelDownloadService = class {
578
632
  const modelPath = this.modelFilePath(entry, selectedFormat);
579
633
  if (!fs.existsSync(modelPath)) return false;
580
634
  if (formatEntry.isDirectory) return fs.existsSync(path$1.join(modelPath, "Manifest.json"));
581
- return fs.statSync(modelPath).size > 0;
635
+ if (fs.statSync(modelPath).size <= 0) return false;
636
+ return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(path$1.join(this.modelsDir, f)));
582
637
  }
583
638
  /** Get the catalog entry for a model by ID. */
584
639
  getEntry(modelId) {
@@ -665,4 +720,4 @@ var ModelDownloadService = class {
665
720
  }
666
721
  };
667
722
  //#endregion
668
- export { ensureModel as a, isModelDownloaded as c, createAuthenticatedFileServer as d, parseRangeHeader as f, downloadModel as i, createFileDataPlaneHandler as l, resolveFilePath as m, deleteModelFromDisk as n, fetchJson as o, parseTokenizedUrl as p, downloadFile as r, getModelFilePath as s, ModelDownloadService as t, contentTypeFor as u };
723
+ export { downloadModel as a, getModelFilePath as c, contentTypeFor as d, createAuthenticatedFileServer as f, resolveFilePath as h, downloadFile as i, isModelDownloaded as l, parseTokenizedUrl as m, collectModelFiles as n, ensureModel as o, parseRangeHeader as p, deleteModelFromDisk as r, fetchJson as s, ModelDownloadService as t, createFileDataPlaneHandler as u };
@@ -301,6 +301,42 @@ function createFileDataPlaneHandler(opts) {
301
301
  }
302
302
  //#endregion
303
303
  //#region src/download/model-downloader.ts
304
+ function isNonEmptyFile(filePath) {
305
+ return node_fs.existsSync(filePath) && node_fs.statSync(filePath).size > 0;
306
+ }
307
+ /**
308
+ * Sibling files of a single-file (non-directory) format — extra files the
309
+ * format needs, fetched from the same remote directory as `url` and stored
310
+ * flat alongside the main file in `modelsDir`. Catalog-declared via
311
+ * `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
312
+ * `.xml`. The downloader stays format-agnostic; the convention lives in the
313
+ * catalog data (like `MLPACKAGE_FILES` for the directory case).
314
+ */
315
+ function siblingFilesFor(formatEntry) {
316
+ return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
317
+ }
318
+ /** Resolve a sibling's remote URL relative to the main file's directory. */
319
+ function siblingUrl(mainUrl, sibling) {
320
+ return mainUrl.replace(/[^/]+$/, sibling);
321
+ }
322
+ /**
323
+ * The relative filenames a model format occupies under `modelsDir` — the main
324
+ * file (URL basename, or `${id}.${format}` fallback), its declared siblings
325
+ * (e.g. the OpenVINO `.bin` next to the `.xml`), and any `extraFiles`. For a
326
+ * directory bundle the single entry is the bundle dir itself (tar/extract
327
+ * recurse into it).
328
+ *
329
+ * Used by model distribution (P2): the hub tars exactly these names from its
330
+ * `modelsDir` and the agent untars them into its own `modelsDir`, so both ends
331
+ * agree on the on-disk layout without re-deriving it.
332
+ */
333
+ function collectModelFiles(entry, format) {
334
+ const formatEntry = entry.formats[format];
335
+ if (!formatEntry) return [];
336
+ const names = [formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`, ...siblingFilesFor(formatEntry)];
337
+ for (const extra of entry.extraFiles ?? []) names.push(extra.filename);
338
+ return names;
339
+ }
304
340
  /** Build fetch headers, including HF auth token for huggingface.co URLs */
305
341
  function buildHeaders(url) {
306
342
  const headers = { "User-Agent": "CamStack/1.0" };
@@ -466,14 +502,18 @@ async function ensureModel(modelsDir, entry, format, onProgress) {
466
502
  if (entry.extraFiles) for (const extra of entry.extraFiles) await downloadFile(extra.url, node_path.join(modelsDir, extra.filename));
467
503
  const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
468
504
  const modelPath = node_path.join(modelsDir, filename);
505
+ const siblings = siblingFilesFor(formatEntry);
469
506
  if (node_fs.existsSync(modelPath)) if (formatEntry.isDirectory && !node_fs.existsSync(node_path.join(modelPath, "Manifest.json"))) node_fs.rmSync(modelPath, {
470
507
  recursive: true,
471
508
  force: true
472
509
  });
473
- else return modelPath;
510
+ else if (siblings.some((f) => !isNonEmptyFile(node_path.join(modelsDir, f)))) {} else return modelPath;
474
511
  node_fs.mkdirSync(modelsDir, { recursive: true });
475
512
  if (formatEntry.isDirectory) await downloadDirectory(formatEntry.url, modelPath, formatEntry.files, onProgress);
476
- else await downloadFile(formatEntry.url, modelPath, (downloaded, total) => onProgress?.(downloaded, total === 0 ? void 0 : total));
513
+ else {
514
+ await downloadFile(formatEntry.url, modelPath, (downloaded, total) => onProgress?.(downloaded, total === 0 ? void 0 : total));
515
+ for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), node_path.join(modelsDir, sibling));
516
+ }
477
517
  return modelPath;
478
518
  }
479
519
  /** Compute the on-disk path for a given model + format, even when not yet downloaded. */
@@ -490,17 +530,25 @@ function isModelDownloaded(modelsDir, entry, format) {
490
530
  const modelPath = getModelFilePath(modelsDir, entry, format);
491
531
  if (!modelPath || !node_fs.existsSync(modelPath)) return false;
492
532
  if (formatEntry.isDirectory) return node_fs.existsSync(node_path.join(modelPath, "Manifest.json"));
493
- return node_fs.statSync(modelPath).size > 0;
533
+ if (node_fs.statSync(modelPath).size <= 0) return false;
534
+ return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(node_path.join(modelsDir, f)));
494
535
  }
495
536
  /** Remove the on-disk model file/directory. Returns true if something was deleted. */
496
537
  function deleteModelFromDisk(modelsDir, entry, format) {
497
538
  const modelPath = getModelFilePath(modelsDir, entry, format);
498
539
  if (!modelPath || !node_fs.existsSync(modelPath)) return false;
499
- if (entry.formats[format]?.isDirectory) node_fs.rmSync(modelPath, {
540
+ const formatEntry = entry.formats[format];
541
+ if (formatEntry?.isDirectory) node_fs.rmSync(modelPath, {
500
542
  recursive: true,
501
543
  force: true
502
544
  });
503
- else node_fs.unlinkSync(modelPath);
545
+ else {
546
+ node_fs.unlinkSync(modelPath);
547
+ if (formatEntry) for (const sibling of siblingFilesFor(formatEntry)) {
548
+ const sibPath = node_path.join(modelsDir, sibling);
549
+ if (node_fs.existsSync(sibPath)) node_fs.unlinkSync(sibPath);
550
+ }
551
+ }
504
552
  return true;
505
553
  }
506
554
  //#endregion
@@ -537,15 +585,21 @@ var ModelDownloadService = class {
537
585
  if (!formatEntry) throw new Error(`ModelDownloadService: model "${modelId}" has no ${selectedFormat} format`);
538
586
  await this.ensureExtraFiles(modelId);
539
587
  const modelPath = this.modelFilePath(entry, selectedFormat);
540
- if (node_fs.existsSync(modelPath)) if (formatEntry.isDirectory) if (!node_fs.existsSync(node_path.join(modelPath, "Manifest.json"))) node_fs.rmSync(modelPath, {
541
- recursive: true,
542
- force: true
543
- });
544
- else return modelPath;
545
- else return modelPath;
588
+ const siblings = siblingFilesFor(formatEntry);
589
+ if (node_fs.existsSync(modelPath)) {
590
+ if (formatEntry.isDirectory) if (!node_fs.existsSync(node_path.join(modelPath, "Manifest.json"))) node_fs.rmSync(modelPath, {
591
+ recursive: true,
592
+ force: true
593
+ });
594
+ else return modelPath;
595
+ else if (siblings.every((f) => isNonEmptyFile(node_path.join(this.modelsDir, f)))) return modelPath;
596
+ }
546
597
  node_fs.mkdirSync(this.modelsDir, { recursive: true });
547
598
  if (formatEntry.isDirectory) await this.downloadDirectory(formatEntry.url, modelPath, formatEntry.files, modelId);
548
- else await downloadFile(formatEntry.url, modelPath, this.onProgress ? (downloaded, total) => this.onProgress(modelId, downloaded, total) : void 0);
599
+ else {
600
+ await downloadFile(formatEntry.url, modelPath, this.onProgress ? (downloaded, total) => this.onProgress(modelId, downloaded, total) : void 0);
601
+ for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), node_path.join(this.modelsDir, sibling));
602
+ }
549
603
  return modelPath;
550
604
  }
551
605
  /**
@@ -579,7 +633,8 @@ var ModelDownloadService = class {
579
633
  const modelPath = this.modelFilePath(entry, selectedFormat);
580
634
  if (!node_fs.existsSync(modelPath)) return false;
581
635
  if (formatEntry.isDirectory) return node_fs.existsSync(node_path.join(modelPath, "Manifest.json"));
582
- return node_fs.statSync(modelPath).size > 0;
636
+ if (node_fs.statSync(modelPath).size <= 0) return false;
637
+ return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(node_path.join(this.modelsDir, f)));
583
638
  }
584
639
  /** Get the catalog entry for a model by ID. */
585
640
  getEntry(modelId) {
@@ -672,6 +727,12 @@ Object.defineProperty(exports, "ModelDownloadService", {
672
727
  return ModelDownloadService;
673
728
  }
674
729
  });
730
+ Object.defineProperty(exports, "collectModelFiles", {
731
+ enumerable: true,
732
+ get: function() {
733
+ return collectModelFiles;
734
+ }
735
+ });
675
736
  Object.defineProperty(exports, "contentTypeFor", {
676
737
  enumerable: true,
677
738
  get: function() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.0.8",
3
+ "version": "1.1.1",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",