@camstack/addon-ai 0.4.29 → 0.4.30

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 (3) hide show
  1. package/dist/addon.js +15 -159
  2. package/dist/addon.mjs +27 -170
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -4,14 +4,11 @@ Object.defineProperties(exports, {
4
4
  });
5
5
  const require_chunk = require("./chunk-CVvi-lCc.js");
6
6
  let node_path = require("node:path");
7
- let node_path$1 = require_chunk.__toESM(node_path, 1);
8
7
  node_path = require_chunk.__toESM(node_path);
9
8
  let node_crypto = require("node:crypto");
10
9
  let node_net = require("node:net");
11
10
  let node_fs = require("node:fs");
12
- node_fs = require_chunk.__toESM(node_fs, 1);
13
- let node_util = require("node:util");
14
- let node_zlib = require("node:zlib");
11
+ let _camstack_system_addon_utils = require("@camstack/system/addon-utils");
15
12
  let node_fs_promises = require("node:fs/promises");
16
13
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
17
14
  let node_child_process = require("node:child_process");
@@ -61226,12 +61223,12 @@ var require_auth_config = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
61226
61223
  });
61227
61224
  module.exports = __toCommonJS(auth_config_exports);
61228
61225
  var fs$1 = __toESM(require("fs"));
61229
- var path$2 = __toESM(require("path"));
61226
+ var path$1 = __toESM(require("path"));
61230
61227
  var import_token_util = require_token_util();
61231
61228
  function getAuthConfigPath() {
61232
61229
  const dataDir = (0, import_token_util.getVercelDataDir)();
61233
61230
  if (!dataDir) throw new Error(`Unable to find Vercel CLI data directory. Your platform: ${process.platform}. Supported: darwin, linux, win32.`);
61234
- return path$2.join(dataDir, "auth.json");
61231
+ return path$1.join(dataDir, "auth.json");
61235
61232
  }
61236
61233
  function readAuthConfig() {
61237
61234
  try {
@@ -61246,7 +61243,7 @@ var require_auth_config = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
61246
61243
  }
61247
61244
  function writeAuthConfig(config) {
61248
61245
  const authPath = getAuthConfigPath();
61249
- const authDir = path$2.dirname(authPath);
61246
+ const authDir = path$1.dirname(authPath);
61250
61247
  if (!fs$1.existsSync(authDir)) fs$1.mkdirSync(authDir, {
61251
61248
  mode: 504,
61252
61249
  recursive: true
@@ -61384,7 +61381,7 @@ var require_token_util = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
61384
61381
  saveToken: () => saveToken
61385
61382
  });
61386
61383
  module.exports = __toCommonJS(token_util_exports);
61387
- var path$1 = __toESM(require("path"));
61384
+ var path = __toESM(require("path"));
61388
61385
  var fs = __toESM(require("fs"));
61389
61386
  var import_token_error = require_token_error();
61390
61387
  var import_token_io = require_token_io();
@@ -61395,7 +61392,7 @@ var require_token_util = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
61395
61392
  const vercelFolder = "com.vercel.cli";
61396
61393
  const dataDir = (0, import_token_io.getUserDataDir)();
61397
61394
  if (!dataDir) return null;
61398
- return path$1.join(dataDir, vercelFolder);
61395
+ return path.join(dataDir, vercelFolder);
61399
61396
  }
61400
61397
  async function getVercelToken(options) {
61401
61398
  const authConfig = (0, import_auth_config.readAuthConfig)();
@@ -61443,7 +61440,7 @@ var require_token_util = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
61443
61440
  function findProjectInfo() {
61444
61441
  const dir = (0, import_token_io.findRootDir)();
61445
61442
  if (!dir) throw new import_token_error.VercelOidcTokenError("Unable to find project root directory. Have you linked your project with `vc link?`");
61446
- const prjPath = path$1.join(dir, ".vercel", "project.json");
61443
+ const prjPath = path.join(dir, ".vercel", "project.json");
61447
61444
  if (!fs.existsSync(prjPath)) throw new import_token_error.VercelOidcTokenError("project.json not found, have you linked your project with `vc link?`");
61448
61445
  const prj = JSON.parse(fs.readFileSync(prjPath, "utf8"));
61449
61446
  if (typeof prj.projectId !== "string" && typeof prj.orgId !== "string") throw new TypeError("Expected a string-valued projectId property. Try running `vc link` to re-link your project.");
@@ -61455,9 +61452,9 @@ var require_token_util = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
61455
61452
  function saveToken(token, projectId) {
61456
61453
  const dir = (0, import_token_io.getUserDataDir)();
61457
61454
  if (!dir) throw new import_token_error.VercelOidcTokenError("Unable to find user data directory. Please reach out to Vercel support.");
61458
- const tokenPath = path$1.join(dir, "com.vercel.token", `${projectId}.json`);
61455
+ const tokenPath = path.join(dir, "com.vercel.token", `${projectId}.json`);
61459
61456
  const tokenJson = JSON.stringify(token);
61460
- fs.mkdirSync(path$1.dirname(tokenPath), {
61457
+ fs.mkdirSync(path.dirname(tokenPath), {
61461
61458
  mode: 504,
61462
61459
  recursive: true
61463
61460
  });
@@ -61467,7 +61464,7 @@ var require_token_util = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
61467
61464
  function loadToken(projectId) {
61468
61465
  const dir = (0, import_token_io.getUserDataDir)();
61469
61466
  if (!dir) throw new import_token_error.VercelOidcTokenError("Unable to find user data directory. Please reach out to Vercel support.");
61470
- const tokenPath = path$1.join(dir, "com.vercel.token", `${projectId}.json`);
61467
+ const tokenPath = path.join(dir, "com.vercel.token", `${projectId}.json`);
61471
61468
  if (!fs.existsSync(tokenPath)) return null;
61472
61469
  const token = JSON.parse(fs.readFileSync(tokenPath, "utf8"));
61473
61470
  assertVercelOidcTokenResponse(token);
@@ -73473,147 +73470,6 @@ function entryForRef(ref) {
73473
73470
  };
73474
73471
  }
73475
73472
  //#endregion
73476
- //#region ../system/dist/file-data-plane-BhKdxJgf.mjs
73477
- function isNonEmptyFile(filePath) {
73478
- return node_fs.existsSync(filePath) && node_fs.statSync(filePath).size > 0;
73479
- }
73480
- /**
73481
- * Sibling files of a single-file (non-directory) format — extra files the
73482
- * format needs, fetched from the same remote directory as `url` and stored
73483
- * flat alongside the main file in `modelsDir`. Catalog-declared via
73484
- * `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
73485
- * `.xml`. The downloader stays format-agnostic; the convention lives in the
73486
- * catalog data (like `MLPACKAGE_FILES` for the directory case).
73487
- */
73488
- function siblingFilesFor(formatEntry) {
73489
- return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
73490
- }
73491
- /** Build fetch headers, including HF auth token for huggingface.co URLs */
73492
- function buildHeaders(url) {
73493
- const headers = { "User-Agent": "CamStack/1.0" };
73494
- const hfToken = process.env["HF_TOKEN"] ?? process.env["HUGGING_FACE_HUB_TOKEN"];
73495
- if (hfToken && url.includes("huggingface.co")) headers["Authorization"] = `Bearer ${hfToken}`;
73496
- return headers;
73497
- }
73498
- var DEFAULT_MAX_REDIRECTS = 5;
73499
- function normalizeDownloadOptions(third) {
73500
- if (typeof third === "function") return { onProgress: third };
73501
- return third ?? {};
73502
- }
73503
- function isRedirectStatus(status) {
73504
- return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
73505
- }
73506
- function resolveRedirectUrl(current, location) {
73507
- return new URL(location, current);
73508
- }
73509
- async function downloadFile(url, destPath, onProgressOrOptions) {
73510
- if (node_fs.existsSync(destPath)) return destPath;
73511
- const opts = normalizeDownloadOptions(onProgressOrOptions);
73512
- const fetchImpl = opts.fetchImpl ?? fetch;
73513
- const maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
73514
- node_fs.mkdirSync(node_path$1.dirname(destPath), { recursive: true });
73515
- const tmpPath = destPath + ".downloading";
73516
- try {
73517
- let current = url;
73518
- const seen = /* @__PURE__ */ new Set();
73519
- let response;
73520
- const manual = opts.redirectPolicy !== void 0;
73521
- for (let hop = 0; hop <= maxRedirects; hop++) {
73522
- const parsed = new URL(current);
73523
- if (opts.redirectPolicy) opts.redirectPolicy(parsed, hop);
73524
- if (seen.has(parsed.href)) throw new Error(`Redirect loop detected at ${parsed.href}`);
73525
- seen.add(parsed.href);
73526
- const controller = opts.timeoutMs !== void 0 ? new AbortController() : void 0;
73527
- const timer = controller && opts.timeoutMs !== void 0 ? setTimeout(() => controller.abort(), opts.timeoutMs) : void 0;
73528
- try {
73529
- response = await fetchImpl(current, {
73530
- redirect: manual ? "manual" : "follow",
73531
- headers: buildHeaders(current),
73532
- ...controller ? { signal: controller.signal } : {}
73533
- });
73534
- } finally {
73535
- if (timer) clearTimeout(timer);
73536
- }
73537
- if (manual && isRedirectStatus(response.status)) {
73538
- const location = response.headers.get("location");
73539
- if (!location) throw new Error(`Redirect ${response.status} missing Location header`);
73540
- if (hop >= maxRedirects) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
73541
- current = resolveRedirectUrl(current, location).href;
73542
- continue;
73543
- }
73544
- break;
73545
- }
73546
- if (!response) throw new Error(`No response downloading ${url}`);
73547
- if (manual && isRedirectStatus(response.status)) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
73548
- if (!response.ok) throw new Error(`HTTP ${response.status} downloading ${url}`);
73549
- if (!response.body) throw new Error(`No response body from ${url}`);
73550
- const total = parseInt(response.headers.get("content-length") ?? "0", 10);
73551
- let downloaded = 0;
73552
- const fileStream = node_fs.createWriteStream(tmpPath);
73553
- const reader = response.body.getReader();
73554
- try {
73555
- for (;;) {
73556
- const { done, value } = await reader.read();
73557
- if (done || !value) break;
73558
- downloaded += value.length;
73559
- if (opts.maxBytes !== void 0 && downloaded > opts.maxBytes) throw new Error(`Downloaded ${downloaded} bytes exceeds the ${opts.maxBytes}-byte limit`);
73560
- fileStream.write(value);
73561
- opts.onProgress?.(downloaded, total);
73562
- }
73563
- } finally {
73564
- fileStream.end();
73565
- await new Promise((resolve, reject) => {
73566
- fileStream.on("finish", resolve);
73567
- fileStream.on("error", reject);
73568
- });
73569
- }
73570
- node_fs.renameSync(tmpPath, destPath);
73571
- return destPath;
73572
- } catch (err) {
73573
- try {
73574
- node_fs.unlinkSync(tmpPath);
73575
- } catch {}
73576
- throw err;
73577
- }
73578
- }
73579
- /** Compute the on-disk path for a given model + format, even when not yet downloaded. */
73580
- function getModelFilePath(modelsDir, entry, format) {
73581
- const formatEntry = entry.formats[format];
73582
- if (!formatEntry) return null;
73583
- const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
73584
- return node_path$1.join(modelsDir, filename);
73585
- }
73586
- /** True iff the model file (or `Manifest.json` for directory bundles) exists and is non-empty. */
73587
- function isModelDownloaded(modelsDir, entry, format) {
73588
- const formatEntry = entry.formats[format];
73589
- if (!formatEntry) return false;
73590
- const modelPath = getModelFilePath(modelsDir, entry, format);
73591
- if (!modelPath || !node_fs.existsSync(modelPath)) return false;
73592
- if (formatEntry.isDirectory) return node_fs.existsSync(node_path$1.join(modelPath, "Manifest.json"));
73593
- if (node_fs.statSync(modelPath).size <= 0) return false;
73594
- return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(node_path$1.join(modelsDir, f)));
73595
- }
73596
- /** Remove the on-disk model file/directory. Returns true if something was deleted. */
73597
- function deleteModelFromDisk(modelsDir, entry, format) {
73598
- const modelPath = getModelFilePath(modelsDir, entry, format);
73599
- if (!modelPath || !node_fs.existsSync(modelPath)) return false;
73600
- const formatEntry = entry.formats[format];
73601
- if (formatEntry?.isDirectory) node_fs.rmSync(modelPath, {
73602
- recursive: true,
73603
- force: true
73604
- });
73605
- else {
73606
- node_fs.unlinkSync(modelPath);
73607
- if (formatEntry) for (const sibling of siblingFilesFor(formatEntry)) {
73608
- const sibPath = node_path$1.join(modelsDir, sibling);
73609
- if (node_fs.existsSync(sibPath)) node_fs.unlinkSync(sibPath);
73610
- }
73611
- }
73612
- return true;
73613
- }
73614
- (0, node_util.promisify)(node_zlib.brotliCompress);
73615
- (0, node_util.promisify)(node_zlib.gzip);
73616
- //#endregion
73617
73473
  //#region src/runtime/model-ops.ts
73618
73474
  /**
73619
73475
  * Default `RuntimeModelOps` — the ONLY place the reused object-detection model
@@ -73677,7 +73533,7 @@ function createDefaultModelOps(modelsDir) {
73677
73533
  downloadedBytes: carried,
73678
73534
  ...totalBytes > 0 ? { totalBytes } : {}
73679
73535
  });
73680
- await downloadFile(file.url, file.destPath, (downloaded) => {
73536
+ await (0, _camstack_system_addon_utils.downloadFile)(file.url, file.destPath, (downloaded) => {
73681
73537
  onProgress({
73682
73538
  file: file.filename,
73683
73539
  fileIndex: index + 1,
@@ -73688,18 +73544,18 @@ function createDefaultModelOps(modelsDir) {
73688
73544
  });
73689
73545
  carried += (0, node_fs.existsSync)(file.destPath) ? (0, node_fs.statSync)(file.destPath).size : file.expectedBytes;
73690
73546
  }
73691
- const main = getModelFilePath(modelsDir, entry, GGUF);
73547
+ const main = (0, _camstack_system_addon_utils.getModelFilePath)(modelsDir, entry, GGUF);
73692
73548
  if (main === null) throw new Error(`no gguf path for model ${entry.id}`);
73693
73549
  return main;
73694
73550
  },
73695
- isDownloaded: (entry) => isModelDownloaded(modelsDir, entry, GGUF),
73551
+ isDownloaded: (entry) => (0, _camstack_system_addon_utils.isModelDownloaded)(modelsDir, entry, GGUF),
73696
73552
  pathFor: (entry) => {
73697
- const p = getModelFilePath(modelsDir, entry, GGUF);
73553
+ const p = (0, _camstack_system_addon_utils.getModelFilePath)(modelsDir, entry, GGUF);
73698
73554
  if (p === null) throw new Error(`no gguf path for model ${entry.id}`);
73699
73555
  return p;
73700
73556
  },
73701
73557
  delete: async (entry) => {
73702
- deleteModelFromDisk(modelsDir, entry, GGUF);
73558
+ (0, _camstack_system_addon_utils.deleteModelFromDisk)(modelsDir, entry, GGUF);
73703
73559
  },
73704
73560
  extraFilePath: (_entry, filename) => node_path.join(modelsDir, filename)
73705
73561
  };
package/dist/addon.mjs CHANGED
@@ -1,11 +1,9 @@
1
1
  import { createRequire } from "node:module";
2
- import * as path$1 from "node:path";
2
+ import * as path from "node:path";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import { connect, createServer } from "node:net";
5
- import * as fs from "node:fs";
6
5
  import { createReadStream, existsSync, statSync } from "node:fs";
7
- import { promisify } from "node:util";
8
- import { brotliCompress, gzip } from "node:zlib";
6
+ import { deleteModelFromDisk, downloadFile, getModelFilePath, isModelDownloaded } from "@camstack/system/addon-utils";
9
7
  import * as fsp from "node:fs/promises";
10
8
  import { spawn } from "node:child_process";
11
9
  //#region \0rolldown/runtime.js
@@ -61251,7 +61249,7 @@ var require_auth_config = /* @__PURE__ */ __commonJSMin(((exports, module) => {
61251
61249
  writeAuthConfig: () => writeAuthConfig
61252
61250
  });
61253
61251
  module.exports = __toCommonJS(auth_config_exports);
61254
- var fs$2 = __toESM(__require("fs"));
61252
+ var fs$1 = __toESM(__require("fs"));
61255
61253
  var path$2 = __toESM(__require("path"));
61256
61254
  var import_token_util = require_token_util();
61257
61255
  function getAuthConfigPath() {
@@ -61262,8 +61260,8 @@ var require_auth_config = /* @__PURE__ */ __commonJSMin(((exports, module) => {
61262
61260
  function readAuthConfig() {
61263
61261
  try {
61264
61262
  const authPath = getAuthConfigPath();
61265
- if (!fs$2.existsSync(authPath)) return null;
61266
- const content = fs$2.readFileSync(authPath, "utf8");
61263
+ if (!fs$1.existsSync(authPath)) return null;
61264
+ const content = fs$1.readFileSync(authPath, "utf8");
61267
61265
  if (!content) return null;
61268
61266
  return JSON.parse(content);
61269
61267
  } catch (error) {
@@ -61273,11 +61271,11 @@ var require_auth_config = /* @__PURE__ */ __commonJSMin(((exports, module) => {
61273
61271
  function writeAuthConfig(config) {
61274
61272
  const authPath = getAuthConfigPath();
61275
61273
  const authDir = path$2.dirname(authPath);
61276
- if (!fs$2.existsSync(authDir)) fs$2.mkdirSync(authDir, {
61274
+ if (!fs$1.existsSync(authDir)) fs$1.mkdirSync(authDir, {
61277
61275
  mode: 504,
61278
61276
  recursive: true
61279
61277
  });
61280
- fs$2.writeFileSync(authPath, JSON.stringify(config, null, 2), { mode: 384 });
61278
+ fs$1.writeFileSync(authPath, JSON.stringify(config, null, 2), { mode: 384 });
61281
61279
  }
61282
61280
  function isValidAccessToken(authConfig, expirationBufferMs = 0) {
61283
61281
  if (!authConfig.token) return false;
@@ -61410,8 +61408,8 @@ var require_token_util = /* @__PURE__ */ __commonJSMin(((exports, module) => {
61410
61408
  saveToken: () => saveToken
61411
61409
  });
61412
61410
  module.exports = __toCommonJS(token_util_exports);
61413
- var path = __toESM(__require("path"));
61414
- var fs$1 = __toESM(__require("fs"));
61411
+ var path$1 = __toESM(__require("path"));
61412
+ var fs = __toESM(__require("fs"));
61415
61413
  var import_token_error = require_token_error();
61416
61414
  var import_token_io = require_token_io();
61417
61415
  var import_auth_config = require_auth_config();
@@ -61421,7 +61419,7 @@ var require_token_util = /* @__PURE__ */ __commonJSMin(((exports, module) => {
61421
61419
  const vercelFolder = "com.vercel.cli";
61422
61420
  const dataDir = (0, import_token_io.getUserDataDir)();
61423
61421
  if (!dataDir) return null;
61424
- return path.join(dataDir, vercelFolder);
61422
+ return path$1.join(dataDir, vercelFolder);
61425
61423
  }
61426
61424
  async function getVercelToken(options) {
61427
61425
  const authConfig = (0, import_auth_config.readAuthConfig)();
@@ -61469,9 +61467,9 @@ var require_token_util = /* @__PURE__ */ __commonJSMin(((exports, module) => {
61469
61467
  function findProjectInfo() {
61470
61468
  const dir = (0, import_token_io.findRootDir)();
61471
61469
  if (!dir) throw new import_token_error.VercelOidcTokenError("Unable to find project root directory. Have you linked your project with `vc link?`");
61472
- const prjPath = path.join(dir, ".vercel", "project.json");
61473
- if (!fs$1.existsSync(prjPath)) throw new import_token_error.VercelOidcTokenError("project.json not found, have you linked your project with `vc link?`");
61474
- const prj = JSON.parse(fs$1.readFileSync(prjPath, "utf8"));
61470
+ const prjPath = path$1.join(dir, ".vercel", "project.json");
61471
+ if (!fs.existsSync(prjPath)) throw new import_token_error.VercelOidcTokenError("project.json not found, have you linked your project with `vc link?`");
61472
+ const prj = JSON.parse(fs.readFileSync(prjPath, "utf8"));
61475
61473
  if (typeof prj.projectId !== "string" && typeof prj.orgId !== "string") throw new TypeError("Expected a string-valued projectId property. Try running `vc link` to re-link your project.");
61476
61474
  return {
61477
61475
  projectId: prj.projectId,
@@ -61481,21 +61479,21 @@ var require_token_util = /* @__PURE__ */ __commonJSMin(((exports, module) => {
61481
61479
  function saveToken(token, projectId) {
61482
61480
  const dir = (0, import_token_io.getUserDataDir)();
61483
61481
  if (!dir) throw new import_token_error.VercelOidcTokenError("Unable to find user data directory. Please reach out to Vercel support.");
61484
- const tokenPath = path.join(dir, "com.vercel.token", `${projectId}.json`);
61482
+ const tokenPath = path$1.join(dir, "com.vercel.token", `${projectId}.json`);
61485
61483
  const tokenJson = JSON.stringify(token);
61486
- fs$1.mkdirSync(path.dirname(tokenPath), {
61484
+ fs.mkdirSync(path$1.dirname(tokenPath), {
61487
61485
  mode: 504,
61488
61486
  recursive: true
61489
61487
  });
61490
- fs$1.writeFileSync(tokenPath, tokenJson);
61491
- fs$1.chmodSync(tokenPath, 432);
61488
+ fs.writeFileSync(tokenPath, tokenJson);
61489
+ fs.chmodSync(tokenPath, 432);
61492
61490
  }
61493
61491
  function loadToken(projectId) {
61494
61492
  const dir = (0, import_token_io.getUserDataDir)();
61495
61493
  if (!dir) throw new import_token_error.VercelOidcTokenError("Unable to find user data directory. Please reach out to Vercel support.");
61496
- const tokenPath = path.join(dir, "com.vercel.token", `${projectId}.json`);
61497
- if (!fs$1.existsSync(tokenPath)) return null;
61498
- const token = JSON.parse(fs$1.readFileSync(tokenPath, "utf8"));
61494
+ const tokenPath = path$1.join(dir, "com.vercel.token", `${projectId}.json`);
61495
+ if (!fs.existsSync(tokenPath)) return null;
61496
+ const token = JSON.parse(fs.readFileSync(tokenPath, "utf8"));
61499
61497
  assertVercelOidcTokenResponse(token);
61500
61498
  return token;
61501
61499
  }
@@ -73499,147 +73497,6 @@ function entryForRef(ref) {
73499
73497
  };
73500
73498
  }
73501
73499
  //#endregion
73502
- //#region ../system/dist/file-data-plane-BhKdxJgf.mjs
73503
- function isNonEmptyFile(filePath) {
73504
- return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
73505
- }
73506
- /**
73507
- * Sibling files of a single-file (non-directory) format — extra files the
73508
- * format needs, fetched from the same remote directory as `url` and stored
73509
- * flat alongside the main file in `modelsDir`. Catalog-declared via
73510
- * `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
73511
- * `.xml`. The downloader stays format-agnostic; the convention lives in the
73512
- * catalog data (like `MLPACKAGE_FILES` for the directory case).
73513
- */
73514
- function siblingFilesFor(formatEntry) {
73515
- return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
73516
- }
73517
- /** Build fetch headers, including HF auth token for huggingface.co URLs */
73518
- function buildHeaders(url) {
73519
- const headers = { "User-Agent": "CamStack/1.0" };
73520
- const hfToken = process.env["HF_TOKEN"] ?? process.env["HUGGING_FACE_HUB_TOKEN"];
73521
- if (hfToken && url.includes("huggingface.co")) headers["Authorization"] = `Bearer ${hfToken}`;
73522
- return headers;
73523
- }
73524
- var DEFAULT_MAX_REDIRECTS = 5;
73525
- function normalizeDownloadOptions(third) {
73526
- if (typeof third === "function") return { onProgress: third };
73527
- return third ?? {};
73528
- }
73529
- function isRedirectStatus(status) {
73530
- return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
73531
- }
73532
- function resolveRedirectUrl(current, location) {
73533
- return new URL(location, current);
73534
- }
73535
- async function downloadFile(url, destPath, onProgressOrOptions) {
73536
- if (fs.existsSync(destPath)) return destPath;
73537
- const opts = normalizeDownloadOptions(onProgressOrOptions);
73538
- const fetchImpl = opts.fetchImpl ?? fetch;
73539
- const maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
73540
- fs.mkdirSync(path$1.dirname(destPath), { recursive: true });
73541
- const tmpPath = destPath + ".downloading";
73542
- try {
73543
- let current = url;
73544
- const seen = /* @__PURE__ */ new Set();
73545
- let response;
73546
- const manual = opts.redirectPolicy !== void 0;
73547
- for (let hop = 0; hop <= maxRedirects; hop++) {
73548
- const parsed = new URL(current);
73549
- if (opts.redirectPolicy) opts.redirectPolicy(parsed, hop);
73550
- if (seen.has(parsed.href)) throw new Error(`Redirect loop detected at ${parsed.href}`);
73551
- seen.add(parsed.href);
73552
- const controller = opts.timeoutMs !== void 0 ? new AbortController() : void 0;
73553
- const timer = controller && opts.timeoutMs !== void 0 ? setTimeout(() => controller.abort(), opts.timeoutMs) : void 0;
73554
- try {
73555
- response = await fetchImpl(current, {
73556
- redirect: manual ? "manual" : "follow",
73557
- headers: buildHeaders(current),
73558
- ...controller ? { signal: controller.signal } : {}
73559
- });
73560
- } finally {
73561
- if (timer) clearTimeout(timer);
73562
- }
73563
- if (manual && isRedirectStatus(response.status)) {
73564
- const location = response.headers.get("location");
73565
- if (!location) throw new Error(`Redirect ${response.status} missing Location header`);
73566
- if (hop >= maxRedirects) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
73567
- current = resolveRedirectUrl(current, location).href;
73568
- continue;
73569
- }
73570
- break;
73571
- }
73572
- if (!response) throw new Error(`No response downloading ${url}`);
73573
- if (manual && isRedirectStatus(response.status)) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
73574
- if (!response.ok) throw new Error(`HTTP ${response.status} downloading ${url}`);
73575
- if (!response.body) throw new Error(`No response body from ${url}`);
73576
- const total = parseInt(response.headers.get("content-length") ?? "0", 10);
73577
- let downloaded = 0;
73578
- const fileStream = fs.createWriteStream(tmpPath);
73579
- const reader = response.body.getReader();
73580
- try {
73581
- for (;;) {
73582
- const { done, value } = await reader.read();
73583
- if (done || !value) break;
73584
- downloaded += value.length;
73585
- if (opts.maxBytes !== void 0 && downloaded > opts.maxBytes) throw new Error(`Downloaded ${downloaded} bytes exceeds the ${opts.maxBytes}-byte limit`);
73586
- fileStream.write(value);
73587
- opts.onProgress?.(downloaded, total);
73588
- }
73589
- } finally {
73590
- fileStream.end();
73591
- await new Promise((resolve, reject) => {
73592
- fileStream.on("finish", resolve);
73593
- fileStream.on("error", reject);
73594
- });
73595
- }
73596
- fs.renameSync(tmpPath, destPath);
73597
- return destPath;
73598
- } catch (err) {
73599
- try {
73600
- fs.unlinkSync(tmpPath);
73601
- } catch {}
73602
- throw err;
73603
- }
73604
- }
73605
- /** Compute the on-disk path for a given model + format, even when not yet downloaded. */
73606
- function getModelFilePath(modelsDir, entry, format) {
73607
- const formatEntry = entry.formats[format];
73608
- if (!formatEntry) return null;
73609
- const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
73610
- return path$1.join(modelsDir, filename);
73611
- }
73612
- /** True iff the model file (or `Manifest.json` for directory bundles) exists and is non-empty. */
73613
- function isModelDownloaded(modelsDir, entry, format) {
73614
- const formatEntry = entry.formats[format];
73615
- if (!formatEntry) return false;
73616
- const modelPath = getModelFilePath(modelsDir, entry, format);
73617
- if (!modelPath || !fs.existsSync(modelPath)) return false;
73618
- if (formatEntry.isDirectory) return fs.existsSync(path$1.join(modelPath, "Manifest.json"));
73619
- if (fs.statSync(modelPath).size <= 0) return false;
73620
- return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(path$1.join(modelsDir, f)));
73621
- }
73622
- /** Remove the on-disk model file/directory. Returns true if something was deleted. */
73623
- function deleteModelFromDisk(modelsDir, entry, format) {
73624
- const modelPath = getModelFilePath(modelsDir, entry, format);
73625
- if (!modelPath || !fs.existsSync(modelPath)) return false;
73626
- const formatEntry = entry.formats[format];
73627
- if (formatEntry?.isDirectory) fs.rmSync(modelPath, {
73628
- recursive: true,
73629
- force: true
73630
- });
73631
- else {
73632
- fs.unlinkSync(modelPath);
73633
- if (formatEntry) for (const sibling of siblingFilesFor(formatEntry)) {
73634
- const sibPath = path$1.join(modelsDir, sibling);
73635
- if (fs.existsSync(sibPath)) fs.unlinkSync(sibPath);
73636
- }
73637
- }
73638
- return true;
73639
- }
73640
- promisify(brotliCompress);
73641
- promisify(gzip);
73642
- //#endregion
73643
73500
  //#region src/runtime/model-ops.ts
73644
73501
  /**
73645
73502
  * Default `RuntimeModelOps` — the ONLY place the reused object-detection model
@@ -73675,14 +73532,14 @@ function planFiles(modelsDir, entry) {
73675
73532
  const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${GGUF}`;
73676
73533
  out.push({
73677
73534
  url: formatEntry.url,
73678
- destPath: path$1.join(modelsDir, filename),
73535
+ destPath: path.join(modelsDir, filename),
73679
73536
  filename,
73680
73537
  expectedBytes: formatEntry.sizeMB * BYTES_PER_MB
73681
73538
  });
73682
73539
  }
73683
73540
  for (const extra of entry.extraFiles ?? []) out.push({
73684
73541
  url: extra.url,
73685
- destPath: path$1.join(modelsDir, extra.filename),
73542
+ destPath: path.join(modelsDir, extra.filename),
73686
73543
  filename: extra.filename,
73687
73544
  expectedBytes: extra.sizeMB * BYTES_PER_MB
73688
73545
  });
@@ -73727,7 +73584,7 @@ function createDefaultModelOps(modelsDir) {
73727
73584
  delete: async (entry) => {
73728
73585
  deleteModelFromDisk(modelsDir, entry, GGUF);
73729
73586
  },
73730
- extraFilePath: (_entry, filename) => path$1.join(modelsDir, filename)
73587
+ extraFilePath: (_entry, filename) => path.join(modelsDir, filename)
73731
73588
  };
73732
73589
  }
73733
73590
  //#endregion
@@ -74437,7 +74294,7 @@ function createLlmRuntimeProvider(deps) {
74437
74294
  deleteModel: async ({ file }) => {
74438
74295
  const loaded = deps.supervisor.status().modelPath;
74439
74296
  if (loaded !== void 0 && basename(loaded) === file) throw new Error(`cannot delete ${file}: loaded by the running runtime`);
74440
- await fsp.rm(path$1.join(deps.modelsDir, file), { force: true });
74297
+ await fsp.rm(path.join(deps.modelsDir, file), { force: true });
74441
74298
  },
74442
74299
  listLocalModels: async () => {
74443
74300
  return (await listGgufFiles(deps.modelsDir)).map((f) => {
@@ -74445,7 +74302,7 @@ function createLlmRuntimeProvider(deps) {
74445
74302
  return {
74446
74303
  file: f.file,
74447
74304
  sizeBytes: f.sizeBytes,
74448
- path: path$1.join(deps.modelsDir, f.file),
74305
+ path: path.join(deps.modelsDir, f.file),
74449
74306
  ...catalogId !== void 0 ? { catalogId } : {}
74450
74307
  };
74451
74308
  });
@@ -74470,7 +74327,7 @@ async function listGgufFiles(dir) {
74470
74327
  for (const name of names) {
74471
74328
  if (!name.endsWith(".gguf")) continue;
74472
74329
  try {
74473
- const stat = await fsp.stat(path$1.join(dir, name));
74330
+ const stat = await fsp.stat(path.join(dir, name));
74474
74331
  out.push({
74475
74332
  file: name,
74476
74333
  sizeBytes: stat.size
@@ -75804,7 +75661,7 @@ var AiAddon = class extends BaseAddon {
75804
75661
  this.supervisor = supervisor;
75805
75662
  const settingsPort = api !== void 0 ? createApiSettingsStorePort(api) : createMemorySettingsStorePort();
75806
75663
  if (api === void 0) logger.warn("addon-ai: no ctx.api — profiles are in-memory only");
75807
- const binDir = path$1.join(ctx.nodeDataDir, "bin");
75664
+ const binDir = path.join(ctx.nodeDataDir, "bin");
75808
75665
  const { ensureLlamaServer } = await import("./ensure-llama-server-D_uzfSep.mjs").then((n) => n.r);
75809
75666
  const assembly = await assembleAi({
75810
75667
  nodeId: ownNodeId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.4.29",
3
+ "version": "0.4.30",
4
4
  "description": "AI addon for CamStack — the `llm` collection provider (cloud, LAN, and camstack-managed local llama.cpp profiles) plus the per-node `llm-runtime` managed executor.",
5
5
  "keywords": [
6
6
  "camstack",