@camstack/addon-post-analysis 1.2.135 → 1.2.137

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.
@@ -2,17 +2,13 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-kn_4KEmG.js");
5
+ const require_dist = require("../dist-5kMCMfln.js");
6
6
  let node_fs = require("node:fs");
7
- let node_fs$1 = require_dist.__toESM(node_fs, 1);
8
7
  node_fs = require_dist.__toESM(node_fs);
9
8
  let node_path = require("node:path");
10
- let node_path$1 = require_dist.__toESM(node_path, 1);
11
9
  node_path = require_dist.__toESM(node_path);
12
- require("node:crypto");
13
10
  let node_child_process = require("node:child_process");
14
- let node_util = require("node:util");
15
- let node_zlib = require("node:zlib");
11
+ let _camstack_system_addon_utils = require("@camstack/system/addon-utils");
16
12
  //#region src/embedding-encoder/shared/process-memory.ts
17
13
  /**
18
14
  * Read a child process's memory footprint for the pool memory watchdog.
@@ -60,294 +56,6 @@ function readViaPs(pid) {
60
56
  });
61
57
  }
62
58
  //#endregion
63
- //#region ../system/dist/file-data-plane-BhKdxJgf.mjs
64
- function isNonEmptyFile(filePath) {
65
- return node_fs$1.existsSync(filePath) && node_fs$1.statSync(filePath).size > 0;
66
- }
67
- /**
68
- * Sibling files of a single-file (non-directory) format — extra files the
69
- * format needs, fetched from the same remote directory as `url` and stored
70
- * flat alongside the main file in `modelsDir`. Catalog-declared via
71
- * `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
72
- * `.xml`. The downloader stays format-agnostic; the convention lives in the
73
- * catalog data (like `MLPACKAGE_FILES` for the directory case).
74
- */
75
- function siblingFilesFor(formatEntry) {
76
- return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
77
- }
78
- /** Resolve a sibling's remote URL relative to the main file's directory. */
79
- function siblingUrl(mainUrl, sibling) {
80
- return mainUrl.replace(/[^/]+$/, sibling);
81
- }
82
- /** Build fetch headers, including HF auth token for huggingface.co URLs */
83
- function buildHeaders(url) {
84
- const headers = { "User-Agent": "CamStack/1.0" };
85
- const hfToken = process.env["HF_TOKEN"] ?? process.env["HUGGING_FACE_HUB_TOKEN"];
86
- if (hfToken && url.includes("huggingface.co")) headers["Authorization"] = `Bearer ${hfToken}`;
87
- return headers;
88
- }
89
- var DEFAULT_MAX_REDIRECTS = 5;
90
- function normalizeDownloadOptions(third) {
91
- if (typeof third === "function") return { onProgress: third };
92
- return third ?? {};
93
- }
94
- function isRedirectStatus(status) {
95
- return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
96
- }
97
- function resolveRedirectUrl(current, location) {
98
- return new URL(location, current);
99
- }
100
- async function downloadFile(url, destPath, onProgressOrOptions) {
101
- if (node_fs$1.existsSync(destPath)) return destPath;
102
- const opts = normalizeDownloadOptions(onProgressOrOptions);
103
- const fetchImpl = opts.fetchImpl ?? fetch;
104
- const maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
105
- node_fs$1.mkdirSync(node_path$1.dirname(destPath), { recursive: true });
106
- const tmpPath = destPath + ".downloading";
107
- try {
108
- let current = url;
109
- const seen = /* @__PURE__ */ new Set();
110
- let response;
111
- const manual = opts.redirectPolicy !== void 0;
112
- for (let hop = 0; hop <= maxRedirects; hop++) {
113
- const parsed = new URL(current);
114
- if (opts.redirectPolicy) opts.redirectPolicy(parsed, hop);
115
- if (seen.has(parsed.href)) throw new Error(`Redirect loop detected at ${parsed.href}`);
116
- seen.add(parsed.href);
117
- const controller = opts.timeoutMs !== void 0 ? new AbortController() : void 0;
118
- const timer = controller && opts.timeoutMs !== void 0 ? setTimeout(() => controller.abort(), opts.timeoutMs) : void 0;
119
- try {
120
- response = await fetchImpl(current, {
121
- redirect: manual ? "manual" : "follow",
122
- headers: buildHeaders(current),
123
- ...controller ? { signal: controller.signal } : {}
124
- });
125
- } finally {
126
- if (timer) clearTimeout(timer);
127
- }
128
- if (manual && isRedirectStatus(response.status)) {
129
- const location = response.headers.get("location");
130
- if (!location) throw new Error(`Redirect ${response.status} missing Location header`);
131
- if (hop >= maxRedirects) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
132
- current = resolveRedirectUrl(current, location).href;
133
- continue;
134
- }
135
- break;
136
- }
137
- if (!response) throw new Error(`No response downloading ${url}`);
138
- if (manual && isRedirectStatus(response.status)) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
139
- if (!response.ok) throw new Error(`HTTP ${response.status} downloading ${url}`);
140
- if (!response.body) throw new Error(`No response body from ${url}`);
141
- const total = parseInt(response.headers.get("content-length") ?? "0", 10);
142
- let downloaded = 0;
143
- const fileStream = node_fs$1.createWriteStream(tmpPath);
144
- const reader = response.body.getReader();
145
- try {
146
- for (;;) {
147
- const { done, value } = await reader.read();
148
- if (done || !value) break;
149
- downloaded += value.length;
150
- if (opts.maxBytes !== void 0 && downloaded > opts.maxBytes) throw new Error(`Downloaded ${downloaded} bytes exceeds the ${opts.maxBytes}-byte limit`);
151
- fileStream.write(value);
152
- opts.onProgress?.(downloaded, total);
153
- }
154
- } finally {
155
- fileStream.end();
156
- await new Promise((resolve, reject) => {
157
- fileStream.on("finish", resolve);
158
- fileStream.on("error", reject);
159
- });
160
- }
161
- node_fs$1.renameSync(tmpPath, destPath);
162
- return destPath;
163
- } catch (err) {
164
- try {
165
- node_fs$1.unlinkSync(tmpPath);
166
- } catch {}
167
- throw err;
168
- }
169
- }
170
- /**
171
- * Fetch JSON from a URL using native fetch().
172
- */
173
- async function fetchJson(url) {
174
- const response = await fetch(url, {
175
- redirect: "follow",
176
- headers: buildHeaders(url)
177
- });
178
- if (!response.ok) throw new Error(`HTTP ${response.status} fetching ${url}`);
179
- return response.json();
180
- }
181
- /**
182
- * Unified model download service.
183
- *
184
- * Handles downloading model files and extra files (labels, dicts) from a
185
- * catalog of ModelCatalogEntry items. Supports single-file models and
186
- * directory bundles (e.g., .mlpackage for CoreML).
187
- *
188
- * Addons use this via `context.models.ensure(modelId, format)`.
189
- */
190
- var ModelDownloadService = class {
191
- modelsDir;
192
- onProgress;
193
- catalog;
194
- constructor(modelsDir, catalog, onProgress) {
195
- this.modelsDir = modelsDir;
196
- this.onProgress = onProgress;
197
- const map = /* @__PURE__ */ new Map();
198
- for (const entry of catalog) map.set(entry.id, entry);
199
- this.catalog = map;
200
- }
201
- /**
202
- * Ensure a model (and its extra files) is downloaded.
203
- * Returns the local filesystem path to the model file/directory.
204
- */
205
- async ensure(modelId, format) {
206
- const entry = this.catalog.get(modelId);
207
- if (!entry) throw new Error(`ModelDownloadService: unknown model "${modelId}"`);
208
- const selectedFormat = format ?? this.pickDefaultFormat(entry);
209
- const formatEntry = entry.formats[selectedFormat];
210
- if (!formatEntry) throw new Error(`ModelDownloadService: model "${modelId}" has no ${selectedFormat} format`);
211
- await this.ensureExtraFiles(modelId);
212
- const modelPath = this.modelFilePath(entry, selectedFormat);
213
- const siblings = siblingFilesFor(formatEntry);
214
- if (node_fs$1.existsSync(modelPath)) {
215
- if (formatEntry.isDirectory) if (!node_fs$1.existsSync(node_path$1.join(modelPath, "Manifest.json"))) node_fs$1.rmSync(modelPath, {
216
- recursive: true,
217
- force: true
218
- });
219
- else return modelPath;
220
- else if (siblings.every((f) => isNonEmptyFile(node_path$1.join(this.modelsDir, f)))) return modelPath;
221
- }
222
- node_fs$1.mkdirSync(this.modelsDir, { recursive: true });
223
- if (formatEntry.isDirectory) await this.downloadDirectory(formatEntry.url, modelPath, formatEntry.files, modelId);
224
- else {
225
- await downloadFile(formatEntry.url, modelPath, this.onProgress ? (downloaded, total) => this.onProgress(modelId, downloaded, total) : void 0);
226
- for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), node_path$1.join(this.modelsDir, sibling));
227
- }
228
- return modelPath;
229
- }
230
- /**
231
- * Ensure extra files for a model are downloaded.
232
- * Returns the local paths of all extra files.
233
- */
234
- async ensureExtraFiles(modelId) {
235
- const entry = this.catalog.get(modelId);
236
- if (!entry) throw new Error(`ModelDownloadService: unknown model "${modelId}"`);
237
- const extras = entry.extraFiles;
238
- if (!extras || extras.length === 0) return [];
239
- const paths = [];
240
- for (const extra of extras) {
241
- const destPath = node_path$1.join(this.modelsDir, extra.filename);
242
- await downloadFile(extra.url, destPath);
243
- paths.push(destPath);
244
- }
245
- return paths;
246
- }
247
- /** Absolute path to the shared models directory. */
248
- getModelsDir() {
249
- return this.modelsDir;
250
- }
251
- /** Check if a model file is already present on disk. */
252
- isDownloaded(modelId, format) {
253
- const entry = this.catalog.get(modelId);
254
- if (!entry) return false;
255
- const selectedFormat = format ?? this.pickDefaultFormat(entry);
256
- const formatEntry = entry.formats[selectedFormat];
257
- if (!formatEntry) return false;
258
- const modelPath = this.modelFilePath(entry, selectedFormat);
259
- if (!node_fs$1.existsSync(modelPath)) return false;
260
- if (formatEntry.isDirectory) return node_fs$1.existsSync(node_path$1.join(modelPath, "Manifest.json"));
261
- if (node_fs$1.statSync(modelPath).size <= 0) return false;
262
- return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(node_path$1.join(this.modelsDir, f)));
263
- }
264
- /** Get the catalog entry for a model by ID. */
265
- getEntry(modelId) {
266
- return this.catalog.get(modelId);
267
- }
268
- pickDefaultFormat(entry) {
269
- for (const fmt of [
270
- "onnx",
271
- "coreml",
272
- "openvino",
273
- "tflite",
274
- "pt"
275
- ]) if (entry.formats[fmt]) return fmt;
276
- const first = Object.keys(entry.formats)[0];
277
- if (first) return first;
278
- throw new Error(`ModelDownloadService: model "${entry.id}" has no formats`);
279
- }
280
- modelFilePath(entry, format) {
281
- const formatEntry = entry.formats[format];
282
- if (!formatEntry) throw new Error(`Model ${entry.id} has no ${format} format`);
283
- const urlParts = formatEntry.url.split("/");
284
- const filename = urlParts[urlParts.length - 1] ?? `${entry.id}.${format}`;
285
- return node_path$1.join(this.modelsDir, filename);
286
- }
287
- /**
288
- * Download a directory bundle (e.g., .mlpackage) from HuggingFace.
289
- * ATOMIC: downloads to temp dir, renames only on complete success.
290
- */
291
- async downloadDirectory(url, destDir, knownFiles, _modelId) {
292
- const match = url.match(/huggingface\.co\/([^/]+\/[^/]+)\/resolve\/main\/(.+)/);
293
- if (!match) throw new Error(`Cannot parse HuggingFace URL: ${url}`);
294
- const [, repo, dirPath] = match;
295
- let filesToDownload;
296
- if (knownFiles && knownFiles.length > 0) filesToDownload = knownFiles.map((f) => ({
297
- relativePath: f,
298
- fileUrl: `https://huggingface.co/${repo}/resolve/main/${dirPath}/${f}`
299
- }));
300
- else {
301
- const hfFiles = await this.listHfFiles(repo, dirPath);
302
- if (hfFiles.length === 0) throw new Error(`No files found in HuggingFace directory: ${dirPath}`);
303
- filesToDownload = hfFiles.map((f) => ({
304
- relativePath: f.path.substring(dirPath.length + 1),
305
- fileUrl: `https://huggingface.co/${repo}/resolve/main/${f.path}`
306
- }));
307
- }
308
- const tmpDir = destDir + ".downloading";
309
- node_fs$1.rmSync(tmpDir, {
310
- recursive: true,
311
- force: true
312
- });
313
- node_fs$1.mkdirSync(tmpDir, { recursive: true });
314
- try {
315
- for (const file of filesToDownload) {
316
- const destPath = node_path$1.join(tmpDir, file.relativePath);
317
- node_fs$1.mkdirSync(node_path$1.dirname(destPath), { recursive: true });
318
- await downloadFile(file.fileUrl, destPath);
319
- }
320
- node_fs$1.rmSync(destDir, {
321
- recursive: true,
322
- force: true
323
- });
324
- node_fs$1.renameSync(tmpDir, destDir);
325
- } catch (err) {
326
- node_fs$1.rmSync(tmpDir, {
327
- recursive: true,
328
- force: true
329
- });
330
- throw err;
331
- }
332
- }
333
- /** Recursively list all files in a HuggingFace directory via API. */
334
- async listHfFiles(repo, dirPath) {
335
- const entries = await fetchJson(`https://huggingface.co/api/models/${repo}/tree/main/${dirPath}`);
336
- const files = [];
337
- for (const entry of entries) if (entry.type === "file") files.push({
338
- path: entry.path,
339
- size: entry.size ?? entry.lfs?.size ?? 0
340
- });
341
- else if (entry.type === "directory") {
342
- const subFiles = await this.listHfFiles(repo, entry.path);
343
- files.push(...subFiles);
344
- }
345
- return files;
346
- }
347
- };
348
- (0, node_util.promisify)(node_zlib.brotliCompress);
349
- (0, node_util.promisify)(node_zlib.gzip);
350
- //#endregion
351
59
  //#region src/embedding-encoder/catalogs/embedding-models.ts
352
60
  var HF_REPO = "camstack/camstack-models";
353
61
  var hf = (path) => require_dist.hfModelUrl(HF_REPO, path);
@@ -835,15 +543,11 @@ var EmbeddingEncoderAddon = class extends require_dist.BaseAddon {
835
543
  imageEngineInflight = null;
836
544
  textEngineInflight = null;
837
545
  constructor() {
838
- super({
839
- modelId: DEFAULT_CLIP_MODEL,
840
- runtime: "auto",
841
- backend: "cpu"
842
- });
546
+ super({ modelId: DEFAULT_CLIP_MODEL });
843
547
  }
844
548
  async onInitialize() {
845
549
  const modelsDir = await this.resolveModelsDir();
846
- this.models = new ModelDownloadService(modelsDir, [...CLIP_IMAGE_MODELS, ...CLIP_TEXT_MODELS]);
550
+ this.models = new _camstack_system_addon_utils.ModelDownloadService(modelsDir, [...CLIP_IMAGE_MODELS, ...CLIP_TEXT_MODELS]);
847
551
  this.memoryGuard = new require_dist.PoolMemoryWatchdog({
848
552
  policy: require_dist.resolvePoolMemoryPolicy(process.env),
849
553
  log: this.ctx.logger.child("pool-memory"),
@@ -983,11 +687,7 @@ var EmbeddingEncoderAddon = class extends require_dist.BaseAddon {
983
687
  }
984
688
  }
985
689
  async resolveForEntry(entry, target) {
986
- const engineLogger = this.ctx.logger.withTags({
987
- modelId: entry.id,
988
- runtime: this.config.runtime,
989
- backend: this.config.backend
990
- });
690
+ const engineLogger = this.ctx.logger.withTags({ modelId: entry.id });
991
691
  const modelPath = await this.models.ensure(entry.id, "onnx");
992
692
  const pythonPath = await this.ctx.deps.ensurePython();
993
693
  if (!pythonPath) throw new Error("EmbeddingEncoder: embedded Python is unavailable — cannot run ONNX embeddings. ctx.deps.ensurePython() returned null (portable Python download likely failed).");
@@ -1016,50 +716,13 @@ var EmbeddingEncoderAddon = class extends require_dist.BaseAddon {
1016
716
  id: "embedding-encoder-settings",
1017
717
  title: "Embedding Encoder",
1018
718
  columns: 2,
1019
- fields: [
1020
- {
1021
- type: "text",
1022
- key: "modelId",
1023
- label: "Model ID",
1024
- description: "CLIP model identifier to use for image/text embedding",
1025
- default: DEFAULT_CLIP_MODEL
1026
- },
1027
- {
1028
- type: "select",
1029
- key: "runtime",
1030
- label: "Runtime",
1031
- description: "Inference runtime — ONNX runs in the embedded Python.",
1032
- default: "auto",
1033
- options: [{
1034
- label: "Auto",
1035
- value: "auto"
1036
- }, {
1037
- label: "Python",
1038
- value: "python"
1039
- }]
1040
- },
1041
- {
1042
- type: "select",
1043
- key: "backend",
1044
- label: "Backend",
1045
- description: "Hardware backend for inference acceleration",
1046
- default: "cpu",
1047
- options: [
1048
- {
1049
- label: "CPU",
1050
- value: "cpu"
1051
- },
1052
- {
1053
- label: "CUDA",
1054
- value: "cuda"
1055
- },
1056
- {
1057
- label: "CoreML",
1058
- value: "coreml"
1059
- }
1060
- ]
1061
- }
1062
- ]
719
+ fields: [{
720
+ type: "text",
721
+ key: "modelId",
722
+ label: "Model ID",
723
+ description: "CLIP model identifier to use for image/text embedding",
724
+ default: DEFAULT_CLIP_MODEL
725
+ }]
1063
726
  }] });
1064
727
  }
1065
728
  async onConfigChanged() {}