@infersec/conduit 1.106.0 → 1.108.0

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/cli.js CHANGED
@@ -10,7 +10,7 @@ import path$1, { join, win32, posix, dirname, resolve as resolve$1, basename, se
10
10
  import * as require$$3$4 from 'node:fs';
11
11
  import require$$3__default, { existsSync, createWriteStream, statSync, readFileSync, appendFileSync, writeFileSync, createReadStream } from 'node:fs';
12
12
  import process$3, { platform, hrtime, execPath, execArgv } from 'node:process';
13
- import crypto from 'node:crypto';
13
+ import crypto, { randomUUID } from 'node:crypto';
14
14
  import require$$0$h, { Readable, Transform, PassThrough, getDefaultHighWaterMark, Duplex, Writable } from 'node:stream';
15
15
  import require$$1$a, { setDefaultResultOrder } from 'node:dns';
16
16
  import require$$0$9 from 'os';
@@ -21288,6 +21288,42 @@ object$1({
21288
21288
  name: ResourceNameSchema
21289
21289
  });
21290
21290
 
21291
+ const FitMethodSchema = _enum$1(["gguf-header", "model-config", "parameter-heuristic"]);
21292
+ const FitConfidenceSchema = _enum$1(["high", "low", "medium"]);
21293
+ object$1({
21294
+ architecture: string$2().nullable(),
21295
+ contextTrainTokens: number$1().int().positive().nullable(),
21296
+ embeddingLength: number$1().int().positive().nullable(),
21297
+ headCount: number$1().int().positive().nullable(),
21298
+ headCountKV: number$1().int().positive().nullable(),
21299
+ headDim: number$1().int().positive().nullable(),
21300
+ layerCount: number$1().int().positive().nullable(),
21301
+ method: FitMethodSchema,
21302
+ probedAt: string$2(),
21303
+ unresolved: boolean$1().optional()
21304
+ });
21305
+ const FitMemoryDomainKindSchema = _enum$1(["gpu", "system", "unified"]);
21306
+ const FitMemoryDomainSchema = object$1({
21307
+ capacityBytes: number$1().int().nonnegative(),
21308
+ domainId: string$2(),
21309
+ kind: FitMemoryDomainKindSchema,
21310
+ label: string$2(),
21311
+ remainingBytes: number$1().int(),
21312
+ requiredBytes: number$1().int().nonnegative(),
21313
+ reserveBytes: number$1().int().nonnegative()
21314
+ });
21315
+ object$1({
21316
+ confidence: FitConfidenceSchema,
21317
+ contextTokens: number$1().int().positive(),
21318
+ deficitBytes: number$1().int().nonnegative(),
21319
+ domains: array(FitMemoryDomainSchema),
21320
+ engine: LLMEngineSchema,
21321
+ fits: boolean$1(),
21322
+ limitingDomain: string$2().nullable(),
21323
+ method: FitMethodSchema,
21324
+ totalRequiredBytes: number$1().int().nonnegative()
21325
+ });
21326
+
21291
21327
  const ENGINE_API_COMPATIBILITY = {
21292
21328
  exllamav3: {
21293
21329
  nativeAnthropicMessages: false,
@@ -21620,6 +21656,7 @@ const ModelEndpointCallConfigSchema = object$1({
21620
21656
  description: string$2().min(1),
21621
21657
  endpointId: string$2().min(1),
21622
21658
  inputSchema: record(string$2(), unknown()).optional(),
21659
+ maxTokens: number$1().int().positive().optional(),
21623
21660
  outputSchema: record(string$2(), unknown()).optional(),
21624
21661
  systemPrompt: string$2().optional(),
21625
21662
  type: literal("model-endpoint-call")
@@ -21628,6 +21665,7 @@ const ModelSourceCallConfigSchema = object$1({
21628
21665
  description: string$2().min(1),
21629
21666
  groupId: ULIDSchema.optional(),
21630
21667
  inputSchema: record(string$2(), unknown()).optional(),
21668
+ maxTokens: number$1().int().positive().optional(),
21631
21669
  outputSchema: record(string$2(), unknown()).optional(),
21632
21670
  sourceId: ULIDSchema.optional(),
21633
21671
  systemPrompt: string$2().optional(),
@@ -123295,6 +123333,181 @@ async function downloadFileWithRange({ accessToken, filePath, fileSize, modelSlu
123295
123333
  throw new Error(errorMessage);
123296
123334
  }
123297
123335
 
123336
+ const RESTORE_RETRY_ATTEMPTS = 50;
123337
+ const RESTORE_RETRY_DELAY_MS = 5;
123338
+ function errorCode(error) {
123339
+ if (typeof error === "object" && error !== null) {
123340
+ const code = error.code;
123341
+ if (typeof code === "string")
123342
+ return code;
123343
+ }
123344
+ return null;
123345
+ }
123346
+ function isFileExistsError(error) {
123347
+ return errorCode(error) === "EEXIST";
123348
+ }
123349
+ function isFileNotFoundError(error) {
123350
+ return errorCode(error) === "ENOENT";
123351
+ }
123352
+ async function readLockContents({ lockFilePath }) {
123353
+ try {
123354
+ const raw = await readFile(lockFilePath, "utf-8");
123355
+ const data = JSON.parse(raw);
123356
+ if (typeof data.acquiredAt !== "number" || typeof data.token !== "string")
123357
+ return null;
123358
+ return data;
123359
+ }
123360
+ catch {
123361
+ return null;
123362
+ }
123363
+ }
123364
+ async function observeLock({ lockFilePath }) {
123365
+ const [contents, stats] = await Promise.all([
123366
+ readLockContents({ lockFilePath }),
123367
+ stat(lockFilePath).catch(error => {
123368
+ if (isFileNotFoundError(error))
123369
+ return null;
123370
+ throw error;
123371
+ })
123372
+ ]);
123373
+ return {
123374
+ contents,
123375
+ exists: stats !== null,
123376
+ mtimeAgeMs: stats ? Date.now() - stats.mtimeMs : null
123377
+ };
123378
+ }
123379
+ /**
123380
+ * Remove a lock file only if it is the exact instance that was observed and
123381
+ * verified. The candidate is first renamed to a unique quarantine path
123382
+ * (rename is atomic, so exactly one competing process can isolate a given
123383
+ * instance); the quarantined file is then re-verified against the expected
123384
+ * identity. A mismatch means a replacement lock was captured by the rename -
123385
+ * it is renamed back into place rather than deleted.
123386
+ *
123387
+ * Residual race: a restore rename could theoretically clobber a third lock
123388
+ * created in the microsecond window while the replacement sat in quarantine.
123389
+ * Files alone cannot provide a fully linearizable compare-and-delete; closing
123390
+ * that window entirely would require an OS-level lock primitive (flock).
123391
+ */
123392
+ async function removeVerifiedLockInstance({ isTargetInstance, lockFilePath, logger, quarantinePrefix }) {
123393
+ const quarantinePath = `${lockFilePath}.${quarantinePrefix}-${process.pid}-${randomUUID()}`;
123394
+ try {
123395
+ await rename(lockFilePath, quarantinePath);
123396
+ }
123397
+ catch (error) {
123398
+ if (isFileNotFoundError(error))
123399
+ return false;
123400
+ throw error;
123401
+ }
123402
+ const quarantined = await observeLock({ lockFilePath: quarantinePath });
123403
+ if (isTargetInstance(quarantined)) {
123404
+ await rm(quarantinePath, { force: true });
123405
+ return true;
123406
+ }
123407
+ // A replacement lock was captured - restore it before continuing
123408
+ for (let attempt = 1;; attempt++) {
123409
+ try {
123410
+ await rename(quarantinePath, lockFilePath);
123411
+ return false;
123412
+ }
123413
+ catch (error) {
123414
+ const code = errorCode(error);
123415
+ if (code !== "ENOTEMPTY" && code !== "EEXIST" && code !== "EPERM")
123416
+ throw error;
123417
+ if (attempt >= RESTORE_RETRY_ATTEMPTS) {
123418
+ logger.warn("Failed restoring quarantined replacement lock", {
123419
+ lockFilePath,
123420
+ quarantinePath
123421
+ });
123422
+ return false;
123423
+ }
123424
+ await new Promise(resolve => setTimeout(resolve, RESTORE_RETRY_DELAY_MS));
123425
+ }
123426
+ }
123427
+ }
123428
+ /**
123429
+ * Acquire an exclusive cross-process file lock. Creation uses the "wx" flag
123430
+ * so the check-and-create step is atomic: concurrent processes can never both
123431
+ * observe a missing lock file and both claim it. Each acquisition owns a
123432
+ * unique token; only a lock whose stored token matches the holder's handle
123433
+ * may be released by that holder. An active (non-stale) lock is never broken
123434
+ * because a waiter timed out - the waiter fails instead.
123435
+ *
123436
+ * Staleness is judged per observed instance: a parsed lock is stale when its
123437
+ * recorded acquisition age exceeds staleMs; an unparseable lock (possible
123438
+ * mid-write observation) is stale only when its mtime is itself old, so a
123439
+ * fresh lock can never be quarantined off the back of a partial read.
123440
+ */
123441
+ async function acquireExclusiveFileLock({ lockFilePath, logger, pollIntervalMs, staleMs, timeoutMs }) {
123442
+ await mkdir(dirname(lockFilePath), { recursive: true });
123443
+ const token = `${process.pid}-${randomUUID()}`;
123444
+ const deadline = Date.now() + timeoutMs;
123445
+ while (true) {
123446
+ try {
123447
+ const contents = { acquiredAt: Date.now(), token };
123448
+ await writeFile(lockFilePath, JSON.stringify(contents), { flag: "wx" });
123449
+ logger.info("Acquired download lock", { lockFilePath });
123450
+ return { lockFilePath, token };
123451
+ }
123452
+ catch (error) {
123453
+ if (!isFileExistsError(error))
123454
+ throw error;
123455
+ }
123456
+ const observation = await observeLock({ lockFilePath });
123457
+ if (!observation.exists) {
123458
+ // Lock vanished between create and read - retry creation now
123459
+ continue;
123460
+ }
123461
+ const parsedAge = observation.contents !== null ? Date.now() - observation.contents.acquiredAt : null;
123462
+ const staleByContents = parsedAge !== null && parsedAge >= staleMs;
123463
+ const staleByMtime = observation.contents === null &&
123464
+ observation.mtimeAgeMs !== null &&
123465
+ observation.mtimeAgeMs >= staleMs;
123466
+ if (staleByContents) {
123467
+ const observedToken = observation.contents?.token;
123468
+ logger.info("Stale download lock found, breaking", { lockFilePath });
123469
+ await removeVerifiedLockInstance({
123470
+ isTargetInstance: candidate => candidate.contents?.token === observedToken,
123471
+ lockFilePath,
123472
+ logger,
123473
+ quarantinePrefix: "stale"
123474
+ });
123475
+ continue;
123476
+ }
123477
+ if (staleByMtime) {
123478
+ logger.info("Unreadable stale download lock found, breaking", { lockFilePath });
123479
+ await removeVerifiedLockInstance({
123480
+ isTargetInstance: candidate => candidate.contents === null &&
123481
+ candidate.mtimeAgeMs !== null &&
123482
+ candidate.mtimeAgeMs >= staleMs,
123483
+ lockFilePath,
123484
+ logger,
123485
+ quarantinePrefix: "stale"
123486
+ });
123487
+ continue;
123488
+ }
123489
+ if (Date.now() >= deadline) {
123490
+ throw new Error(`Failed acquiring download lock within ${timeoutMs}ms: ${lockFilePath}`);
123491
+ }
123492
+ logger.info("Download lock held by another process, waiting", {
123493
+ lockAgeMs: parsedAge ?? observation.mtimeAgeMs,
123494
+ lockFilePath
123495
+ });
123496
+ await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
123497
+ }
123498
+ }
123499
+ async function releaseExclusiveFileLock({ handle }) {
123500
+ // Removal is verified against the handle's token at quarantine time, so
123501
+ // a replacement lock created after this holder's release/read can never
123502
+ // be unlinked, and a lock already gone is a no-op
123503
+ await removeVerifiedLockInstance({
123504
+ isTargetInstance: candidate => candidate.contents?.token === handle.token,
123505
+ lockFilePath: handle.lockFilePath,
123506
+ logger: { info: () => { }, warn: () => { } },
123507
+ quarantinePrefix: "release"
123508
+ });
123509
+ }
123510
+
123298
123511
  const EXLLAMAV3_EXECUTABLE = process.env.EXLLAMAV3_EXECUTABLE ?? "python3";
123299
123512
  const SERVER_SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "server.py");
123300
123513
  const DEFAULT_EXLLAMAV3_CONTEXT_LENGTH = 4096;
@@ -123370,7 +123583,6 @@ async function startLlamacpp({ enginePort, targetDirectory }) {
123370
123583
  console.warn(`[llamacpp] Multimodal enabled but no projector file found in ${targetDirectory}. ` +
123371
123584
  "Vision functionality will not work.");
123372
123585
  }
123373
- args.push("--special");
123374
123586
  }
123375
123587
  if (process.env.LLAMACPP_REASONING === "off") {
123376
123588
  args.push("--reasoning", "off");
@@ -123505,6 +123717,7 @@ class ModelManager extends EventEmitter {
123505
123717
  healthPollInterval = null;
123506
123718
  lastEngineError = null;
123507
123719
  lifecycleState = "stopped";
123720
+ downloadLockHandle = null;
123508
123721
  stopRequested = false;
123509
123722
  modelsDirectory;
123510
123723
  constructor({ contextLength, engineConfig, enginePort, engineType, logger, model, root }) {
@@ -123822,35 +124035,13 @@ class ModelManager extends EventEmitter {
123822
124035
  return join(this.modelsDirectory, `${this.uniqueName}.lock`);
123823
124036
  }
123824
124037
  async acquireDownloadLock() {
123825
- await mkdir(this.modelsDirectory, { recursive: true });
123826
- if (existsSync(this.lockFilePath)) {
123827
- const age = await this.getLockAge();
123828
- if (age !== null && age < DOWNLOAD_LOCK_TIMEOUT_MS) {
123829
- this.logger.info("Download lock held by another process, waiting", {
123830
- lockAgeMs: age,
123831
- lockFilePath: this.lockFilePath
123832
- });
123833
- await this.waitForDownloadLock();
123834
- }
123835
- else {
123836
- this.logger.info("Stale download lock found, breaking", {
123837
- lockFilePath: this.lockFilePath
123838
- });
123839
- await this.releaseDownloadLock();
123840
- }
123841
- }
123842
- await writeFile(this.lockFilePath, JSON.stringify({ acquiredAt: Date.now() }));
123843
- this.logger.info("Acquired download lock", { lockFilePath: this.lockFilePath });
123844
- }
123845
- async getLockAge() {
123846
- try {
123847
- const raw = await readFile(this.lockFilePath, "utf-8");
123848
- const data = JSON.parse(raw);
123849
- return Date.now() - data.acquiredAt;
123850
- }
123851
- catch {
123852
- return null;
123853
- }
124038
+ this.downloadLockHandle = await acquireExclusiveFileLock({
124039
+ lockFilePath: this.lockFilePath,
124040
+ logger: this.logger,
124041
+ pollIntervalMs: DOWNLOAD_LOCK_POLL_INTERVAL_MS,
124042
+ staleMs: DOWNLOAD_LOCK_TIMEOUT_MS,
124043
+ timeoutMs: DOWNLOAD_LOCK_TIMEOUT_MS
124044
+ });
123854
124045
  }
123855
124046
  recordEngineError(err) {
123856
124047
  this.lifecycleState = "errored";
@@ -123858,27 +124049,11 @@ class ModelManager extends EventEmitter {
123858
124049
  this.emit("engineError", err);
123859
124050
  }
123860
124051
  async releaseDownloadLock() {
123861
- try {
123862
- await unlink(this.lockFilePath);
123863
- }
123864
- catch {
123865
- // already released
123866
- }
123867
- }
123868
- async waitForDownloadLock() {
123869
- const start = Date.now();
123870
- while (Date.now() - start < DOWNLOAD_LOCK_TIMEOUT_MS) {
123871
- await new Promise(resolve => setTimeout(resolve, DOWNLOAD_LOCK_POLL_INTERVAL_MS));
123872
- if (!existsSync(this.lockFilePath)) {
123873
- return;
123874
- }
123875
- const age = await this.getLockAge();
123876
- if (age === null || age >= DOWNLOAD_LOCK_TIMEOUT_MS) {
123877
- await this.releaseDownloadLock();
123878
- return;
123879
- }
123880
- }
123881
- await this.releaseDownloadLock();
124052
+ const handle = this.downloadLockHandle;
124053
+ if (!handle)
124054
+ return;
124055
+ this.downloadLockHandle = null;
124056
+ await releaseExclusiveFileLock({ handle });
123882
124057
  }
123883
124058
  bindEngineProcessEvents(processManager) {
123884
124059
  let hasTerminated = false;
@@ -137056,6 +137231,59 @@ function registerModelsCommands({ program }) {
137056
137231
  }
137057
137232
  console.log(`Cleared ${targets.length} model(s), freed ${formatBytes(totalBytes)}`);
137058
137233
  });
137234
+ models
137235
+ .command("download")
137236
+ .description("Pre-download a HuggingFace model into the local cache")
137237
+ .requiredOption("--slug <slug>", "Fully qualified model slug, optionally with a quantization variant (owner/repo:Q4_K_M)")
137238
+ .option("--format <format>", "Model format (gguf, safetensors, pytorch, ...)", "gguf")
137239
+ .option("--root <path>", "Root directory (or ROOT_DIRECTORY env)")
137240
+ .option("--token <token>", "HuggingFace access token (or HF_TOKEN env)")
137241
+ .action(async (options) => {
137242
+ const slug = options.slug?.trim() ?? "";
137243
+ if (!slug.includes("/")) {
137244
+ console.error("Slug must be a fully qualified owner/repo slug");
137245
+ process.exitCode = 1;
137246
+ return;
137247
+ }
137248
+ const format = LLMModelFormatSchema.safeParse(options.format);
137249
+ if (!format.success) {
137250
+ console.error(`Invalid format "${options.format}". Must be one of: ${LLMModelFormatSchema.options.join(", ")}`);
137251
+ process.exitCode = 1;
137252
+ return;
137253
+ }
137254
+ const huggingFaceToken = options.token?.trim() || null;
137255
+ const model = {
137256
+ format: format.data,
137257
+ id: slug,
137258
+ multimodalEnabled: false,
137259
+ source: {
137260
+ modelSecret: huggingFaceToken,
137261
+ slug,
137262
+ type: "huggingface"
137263
+ },
137264
+ taskType: "text-generation"
137265
+ };
137266
+ const rootDirectory = getRootDirectory(options.root);
137267
+ const modelsDir = join(rootDirectory, "models");
137268
+ const uniqueName = createModelStorageKey(model);
137269
+ const targetDirectory = join(modelsDir, uniqueName);
137270
+ console.log(`Downloading ${slug} (${format.data})`);
137271
+ console.log(` Target: ${targetDirectory}`);
137272
+ try {
137273
+ await downloadModelViaHuggingFace({
137274
+ format: model.format,
137275
+ huggingFaceToken,
137276
+ modelSlug: slug,
137277
+ progressFilePath: join(modelsDir, `${uniqueName}.progress.json`),
137278
+ targetDirectory
137279
+ });
137280
+ console.log(`Model cached: ${targetDirectory}`);
137281
+ }
137282
+ catch (err) {
137283
+ console.error(`Failed downloading model: ${asError(err).message}`);
137284
+ process.exitCode = 1;
137285
+ }
137286
+ });
137059
137287
  }
137060
137288
 
137061
137289
  function parseAllowList(raw) {
package/dist/cli.sea.cjs CHANGED
@@ -21303,6 +21303,42 @@ object$1({
21303
21303
  name: ResourceNameSchema
21304
21304
  });
21305
21305
 
21306
+ const FitMethodSchema = _enum$1(["gguf-header", "model-config", "parameter-heuristic"]);
21307
+ const FitConfidenceSchema = _enum$1(["high", "low", "medium"]);
21308
+ object$1({
21309
+ architecture: string$2().nullable(),
21310
+ contextTrainTokens: number$1().int().positive().nullable(),
21311
+ embeddingLength: number$1().int().positive().nullable(),
21312
+ headCount: number$1().int().positive().nullable(),
21313
+ headCountKV: number$1().int().positive().nullable(),
21314
+ headDim: number$1().int().positive().nullable(),
21315
+ layerCount: number$1().int().positive().nullable(),
21316
+ method: FitMethodSchema,
21317
+ probedAt: string$2(),
21318
+ unresolved: boolean$1().optional()
21319
+ });
21320
+ const FitMemoryDomainKindSchema = _enum$1(["gpu", "system", "unified"]);
21321
+ const FitMemoryDomainSchema = object$1({
21322
+ capacityBytes: number$1().int().nonnegative(),
21323
+ domainId: string$2(),
21324
+ kind: FitMemoryDomainKindSchema,
21325
+ label: string$2(),
21326
+ remainingBytes: number$1().int(),
21327
+ requiredBytes: number$1().int().nonnegative(),
21328
+ reserveBytes: number$1().int().nonnegative()
21329
+ });
21330
+ object$1({
21331
+ confidence: FitConfidenceSchema,
21332
+ contextTokens: number$1().int().positive(),
21333
+ deficitBytes: number$1().int().nonnegative(),
21334
+ domains: array(FitMemoryDomainSchema),
21335
+ engine: LLMEngineSchema,
21336
+ fits: boolean$1(),
21337
+ limitingDomain: string$2().nullable(),
21338
+ method: FitMethodSchema,
21339
+ totalRequiredBytes: number$1().int().nonnegative()
21340
+ });
21341
+
21306
21342
  const ENGINE_API_COMPATIBILITY = {
21307
21343
  exllamav3: {
21308
21344
  nativeAnthropicMessages: false,
@@ -21635,6 +21671,7 @@ const ModelEndpointCallConfigSchema = object$1({
21635
21671
  description: string$2().min(1),
21636
21672
  endpointId: string$2().min(1),
21637
21673
  inputSchema: record(string$2(), unknown()).optional(),
21674
+ maxTokens: number$1().int().positive().optional(),
21638
21675
  outputSchema: record(string$2(), unknown()).optional(),
21639
21676
  systemPrompt: string$2().optional(),
21640
21677
  type: literal("model-endpoint-call")
@@ -21643,6 +21680,7 @@ const ModelSourceCallConfigSchema = object$1({
21643
21680
  description: string$2().min(1),
21644
21681
  groupId: ULIDSchema.optional(),
21645
21682
  inputSchema: record(string$2(), unknown()).optional(),
21683
+ maxTokens: number$1().int().positive().optional(),
21646
21684
  outputSchema: record(string$2(), unknown()).optional(),
21647
21685
  sourceId: ULIDSchema.optional(),
21648
21686
  systemPrompt: string$2().optional(),
@@ -123310,6 +123348,181 @@ async function downloadFileWithRange({ accessToken, filePath, fileSize, modelSlu
123310
123348
  throw new Error(errorMessage);
123311
123349
  }
123312
123350
 
123351
+ const RESTORE_RETRY_ATTEMPTS = 50;
123352
+ const RESTORE_RETRY_DELAY_MS = 5;
123353
+ function errorCode(error) {
123354
+ if (typeof error === "object" && error !== null) {
123355
+ const code = error.code;
123356
+ if (typeof code === "string")
123357
+ return code;
123358
+ }
123359
+ return null;
123360
+ }
123361
+ function isFileExistsError(error) {
123362
+ return errorCode(error) === "EEXIST";
123363
+ }
123364
+ function isFileNotFoundError(error) {
123365
+ return errorCode(error) === "ENOENT";
123366
+ }
123367
+ async function readLockContents({ lockFilePath }) {
123368
+ try {
123369
+ const raw = await require$$0$m.readFile(lockFilePath, "utf-8");
123370
+ const data = JSON.parse(raw);
123371
+ if (typeof data.acquiredAt !== "number" || typeof data.token !== "string")
123372
+ return null;
123373
+ return data;
123374
+ }
123375
+ catch {
123376
+ return null;
123377
+ }
123378
+ }
123379
+ async function observeLock({ lockFilePath }) {
123380
+ const [contents, stats] = await Promise.all([
123381
+ readLockContents({ lockFilePath }),
123382
+ require$$0$m.stat(lockFilePath).catch(error => {
123383
+ if (isFileNotFoundError(error))
123384
+ return null;
123385
+ throw error;
123386
+ })
123387
+ ]);
123388
+ return {
123389
+ contents,
123390
+ exists: stats !== null,
123391
+ mtimeAgeMs: stats ? Date.now() - stats.mtimeMs : null
123392
+ };
123393
+ }
123394
+ /**
123395
+ * Remove a lock file only if it is the exact instance that was observed and
123396
+ * verified. The candidate is first renamed to a unique quarantine path
123397
+ * (rename is atomic, so exactly one competing process can isolate a given
123398
+ * instance); the quarantined file is then re-verified against the expected
123399
+ * identity. A mismatch means a replacement lock was captured by the rename -
123400
+ * it is renamed back into place rather than deleted.
123401
+ *
123402
+ * Residual race: a restore rename could theoretically clobber a third lock
123403
+ * created in the microsecond window while the replacement sat in quarantine.
123404
+ * Files alone cannot provide a fully linearizable compare-and-delete; closing
123405
+ * that window entirely would require an OS-level lock primitive (flock).
123406
+ */
123407
+ async function removeVerifiedLockInstance({ isTargetInstance, lockFilePath, logger, quarantinePrefix }) {
123408
+ const quarantinePath = `${lockFilePath}.${quarantinePrefix}-${process.pid}-${crypto.randomUUID()}`;
123409
+ try {
123410
+ await require$$0$m.rename(lockFilePath, quarantinePath);
123411
+ }
123412
+ catch (error) {
123413
+ if (isFileNotFoundError(error))
123414
+ return false;
123415
+ throw error;
123416
+ }
123417
+ const quarantined = await observeLock({ lockFilePath: quarantinePath });
123418
+ if (isTargetInstance(quarantined)) {
123419
+ await require$$0$m.rm(quarantinePath, { force: true });
123420
+ return true;
123421
+ }
123422
+ // A replacement lock was captured - restore it before continuing
123423
+ for (let attempt = 1;; attempt++) {
123424
+ try {
123425
+ await require$$0$m.rename(quarantinePath, lockFilePath);
123426
+ return false;
123427
+ }
123428
+ catch (error) {
123429
+ const code = errorCode(error);
123430
+ if (code !== "ENOTEMPTY" && code !== "EEXIST" && code !== "EPERM")
123431
+ throw error;
123432
+ if (attempt >= RESTORE_RETRY_ATTEMPTS) {
123433
+ logger.warn("Failed restoring quarantined replacement lock", {
123434
+ lockFilePath,
123435
+ quarantinePath
123436
+ });
123437
+ return false;
123438
+ }
123439
+ await new Promise(resolve => setTimeout(resolve, RESTORE_RETRY_DELAY_MS));
123440
+ }
123441
+ }
123442
+ }
123443
+ /**
123444
+ * Acquire an exclusive cross-process file lock. Creation uses the "wx" flag
123445
+ * so the check-and-create step is atomic: concurrent processes can never both
123446
+ * observe a missing lock file and both claim it. Each acquisition owns a
123447
+ * unique token; only a lock whose stored token matches the holder's handle
123448
+ * may be released by that holder. An active (non-stale) lock is never broken
123449
+ * because a waiter timed out - the waiter fails instead.
123450
+ *
123451
+ * Staleness is judged per observed instance: a parsed lock is stale when its
123452
+ * recorded acquisition age exceeds staleMs; an unparseable lock (possible
123453
+ * mid-write observation) is stale only when its mtime is itself old, so a
123454
+ * fresh lock can never be quarantined off the back of a partial read.
123455
+ */
123456
+ async function acquireExclusiveFileLock({ lockFilePath, logger, pollIntervalMs, staleMs, timeoutMs }) {
123457
+ await require$$0$m.mkdir(path$1.dirname(lockFilePath), { recursive: true });
123458
+ const token = `${process.pid}-${crypto.randomUUID()}`;
123459
+ const deadline = Date.now() + timeoutMs;
123460
+ while (true) {
123461
+ try {
123462
+ const contents = { acquiredAt: Date.now(), token };
123463
+ await require$$0$m.writeFile(lockFilePath, JSON.stringify(contents), { flag: "wx" });
123464
+ logger.info("Acquired download lock", { lockFilePath });
123465
+ return { lockFilePath, token };
123466
+ }
123467
+ catch (error) {
123468
+ if (!isFileExistsError(error))
123469
+ throw error;
123470
+ }
123471
+ const observation = await observeLock({ lockFilePath });
123472
+ if (!observation.exists) {
123473
+ // Lock vanished between create and read - retry creation now
123474
+ continue;
123475
+ }
123476
+ const parsedAge = observation.contents !== null ? Date.now() - observation.contents.acquiredAt : null;
123477
+ const staleByContents = parsedAge !== null && parsedAge >= staleMs;
123478
+ const staleByMtime = observation.contents === null &&
123479
+ observation.mtimeAgeMs !== null &&
123480
+ observation.mtimeAgeMs >= staleMs;
123481
+ if (staleByContents) {
123482
+ const observedToken = observation.contents?.token;
123483
+ logger.info("Stale download lock found, breaking", { lockFilePath });
123484
+ await removeVerifiedLockInstance({
123485
+ isTargetInstance: candidate => candidate.contents?.token === observedToken,
123486
+ lockFilePath,
123487
+ logger,
123488
+ quarantinePrefix: "stale"
123489
+ });
123490
+ continue;
123491
+ }
123492
+ if (staleByMtime) {
123493
+ logger.info("Unreadable stale download lock found, breaking", { lockFilePath });
123494
+ await removeVerifiedLockInstance({
123495
+ isTargetInstance: candidate => candidate.contents === null &&
123496
+ candidate.mtimeAgeMs !== null &&
123497
+ candidate.mtimeAgeMs >= staleMs,
123498
+ lockFilePath,
123499
+ logger,
123500
+ quarantinePrefix: "stale"
123501
+ });
123502
+ continue;
123503
+ }
123504
+ if (Date.now() >= deadline) {
123505
+ throw new Error(`Failed acquiring download lock within ${timeoutMs}ms: ${lockFilePath}`);
123506
+ }
123507
+ logger.info("Download lock held by another process, waiting", {
123508
+ lockAgeMs: parsedAge ?? observation.mtimeAgeMs,
123509
+ lockFilePath
123510
+ });
123511
+ await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
123512
+ }
123513
+ }
123514
+ async function releaseExclusiveFileLock({ handle }) {
123515
+ // Removal is verified against the handle's token at quarantine time, so
123516
+ // a replacement lock created after this holder's release/read can never
123517
+ // be unlinked, and a lock already gone is a no-op
123518
+ await removeVerifiedLockInstance({
123519
+ isTargetInstance: candidate => candidate.contents?.token === handle.token,
123520
+ lockFilePath: handle.lockFilePath,
123521
+ logger: { info: () => { }, warn: () => { } },
123522
+ quarantinePrefix: "release"
123523
+ });
123524
+ }
123525
+
123313
123526
  const EXLLAMAV3_EXECUTABLE = process.env.EXLLAMAV3_EXECUTABLE ?? "python3";
123314
123527
  const SERVER_SCRIPT = path$1.join(path$1.dirname(require$$0$l.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.sea.cjs', document.baseURI).href)))), "server.py");
123315
123528
  const DEFAULT_EXLLAMAV3_CONTEXT_LENGTH = 4096;
@@ -123385,7 +123598,6 @@ async function startLlamacpp({ enginePort, targetDirectory }) {
123385
123598
  console.warn(`[llamacpp] Multimodal enabled but no projector file found in ${targetDirectory}. ` +
123386
123599
  "Vision functionality will not work.");
123387
123600
  }
123388
- args.push("--special");
123389
123601
  }
123390
123602
  if (process.env.LLAMACPP_REASONING === "off") {
123391
123603
  args.push("--reasoning", "off");
@@ -123520,6 +123732,7 @@ class ModelManager extends EventEmitter {
123520
123732
  healthPollInterval = null;
123521
123733
  lastEngineError = null;
123522
123734
  lifecycleState = "stopped";
123735
+ downloadLockHandle = null;
123523
123736
  stopRequested = false;
123524
123737
  modelsDirectory;
123525
123738
  constructor({ contextLength, engineConfig, enginePort, engineType, logger, model, root }) {
@@ -123837,35 +124050,13 @@ class ModelManager extends EventEmitter {
123837
124050
  return path$1.join(this.modelsDirectory, `${this.uniqueName}.lock`);
123838
124051
  }
123839
124052
  async acquireDownloadLock() {
123840
- await require$$0$m.mkdir(this.modelsDirectory, { recursive: true });
123841
- if (require$$3$4.existsSync(this.lockFilePath)) {
123842
- const age = await this.getLockAge();
123843
- if (age !== null && age < DOWNLOAD_LOCK_TIMEOUT_MS) {
123844
- this.logger.info("Download lock held by another process, waiting", {
123845
- lockAgeMs: age,
123846
- lockFilePath: this.lockFilePath
123847
- });
123848
- await this.waitForDownloadLock();
123849
- }
123850
- else {
123851
- this.logger.info("Stale download lock found, breaking", {
123852
- lockFilePath: this.lockFilePath
123853
- });
123854
- await this.releaseDownloadLock();
123855
- }
123856
- }
123857
- await require$$0$m.writeFile(this.lockFilePath, JSON.stringify({ acquiredAt: Date.now() }));
123858
- this.logger.info("Acquired download lock", { lockFilePath: this.lockFilePath });
123859
- }
123860
- async getLockAge() {
123861
- try {
123862
- const raw = await require$$0$m.readFile(this.lockFilePath, "utf-8");
123863
- const data = JSON.parse(raw);
123864
- return Date.now() - data.acquiredAt;
123865
- }
123866
- catch {
123867
- return null;
123868
- }
124053
+ this.downloadLockHandle = await acquireExclusiveFileLock({
124054
+ lockFilePath: this.lockFilePath,
124055
+ logger: this.logger,
124056
+ pollIntervalMs: DOWNLOAD_LOCK_POLL_INTERVAL_MS,
124057
+ staleMs: DOWNLOAD_LOCK_TIMEOUT_MS,
124058
+ timeoutMs: DOWNLOAD_LOCK_TIMEOUT_MS
124059
+ });
123869
124060
  }
123870
124061
  recordEngineError(err) {
123871
124062
  this.lifecycleState = "errored";
@@ -123873,27 +124064,11 @@ class ModelManager extends EventEmitter {
123873
124064
  this.emit("engineError", err);
123874
124065
  }
123875
124066
  async releaseDownloadLock() {
123876
- try {
123877
- await require$$0$m.unlink(this.lockFilePath);
123878
- }
123879
- catch {
123880
- // already released
123881
- }
123882
- }
123883
- async waitForDownloadLock() {
123884
- const start = Date.now();
123885
- while (Date.now() - start < DOWNLOAD_LOCK_TIMEOUT_MS) {
123886
- await new Promise(resolve => setTimeout(resolve, DOWNLOAD_LOCK_POLL_INTERVAL_MS));
123887
- if (!require$$3$4.existsSync(this.lockFilePath)) {
123888
- return;
123889
- }
123890
- const age = await this.getLockAge();
123891
- if (age === null || age >= DOWNLOAD_LOCK_TIMEOUT_MS) {
123892
- await this.releaseDownloadLock();
123893
- return;
123894
- }
123895
- }
123896
- await this.releaseDownloadLock();
124067
+ const handle = this.downloadLockHandle;
124068
+ if (!handle)
124069
+ return;
124070
+ this.downloadLockHandle = null;
124071
+ await releaseExclusiveFileLock({ handle });
123897
124072
  }
123898
124073
  bindEngineProcessEvents(processManager) {
123899
124074
  let hasTerminated = false;
@@ -157286,6 +157461,59 @@ function registerModelsCommands({ program }) {
157286
157461
  }
157287
157462
  console.log(`Cleared ${targets.length} model(s), freed ${formatBytes(totalBytes)}`);
157288
157463
  });
157464
+ models
157465
+ .command("download")
157466
+ .description("Pre-download a HuggingFace model into the local cache")
157467
+ .requiredOption("--slug <slug>", "Fully qualified model slug, optionally with a quantization variant (owner/repo:Q4_K_M)")
157468
+ .option("--format <format>", "Model format (gguf, safetensors, pytorch, ...)", "gguf")
157469
+ .option("--root <path>", "Root directory (or ROOT_DIRECTORY env)")
157470
+ .option("--token <token>", "HuggingFace access token (or HF_TOKEN env)")
157471
+ .action(async (options) => {
157472
+ const slug = options.slug?.trim() ?? "";
157473
+ if (!slug.includes("/")) {
157474
+ console.error("Slug must be a fully qualified owner/repo slug");
157475
+ process.exitCode = 1;
157476
+ return;
157477
+ }
157478
+ const format = LLMModelFormatSchema.safeParse(options.format);
157479
+ if (!format.success) {
157480
+ console.error(`Invalid format "${options.format}". Must be one of: ${LLMModelFormatSchema.options.join(", ")}`);
157481
+ process.exitCode = 1;
157482
+ return;
157483
+ }
157484
+ const huggingFaceToken = options.token?.trim() || null;
157485
+ const model = {
157486
+ format: format.data,
157487
+ id: slug,
157488
+ multimodalEnabled: false,
157489
+ source: {
157490
+ modelSecret: huggingFaceToken,
157491
+ slug,
157492
+ type: "huggingface"
157493
+ },
157494
+ taskType: "text-generation"
157495
+ };
157496
+ const rootDirectory = getRootDirectory(options.root);
157497
+ const modelsDir = path$1.join(rootDirectory, "models");
157498
+ const uniqueName = createModelStorageKey(model);
157499
+ const targetDirectory = path$1.join(modelsDir, uniqueName);
157500
+ console.log(`Downloading ${slug} (${format.data})`);
157501
+ console.log(` Target: ${targetDirectory}`);
157502
+ try {
157503
+ await downloadModelViaHuggingFace({
157504
+ format: model.format,
157505
+ huggingFaceToken,
157506
+ modelSlug: slug,
157507
+ progressFilePath: path$1.join(modelsDir, `${uniqueName}.progress.json`),
157508
+ targetDirectory
157509
+ });
157510
+ console.log(`Model cached: ${targetDirectory}`);
157511
+ }
157512
+ catch (err) {
157513
+ console.error(`Failed downloading model: ${asError(err).message}`);
157514
+ process.exitCode = 1;
157515
+ }
157516
+ });
157289
157517
  }
157290
157518
 
157291
157519
  function parseAllowList(raw) {
@@ -22,6 +22,7 @@ export declare class ModelManager extends EventEmitter<ModelManagerEvents> {
22
22
  private healthPollInterval;
23
23
  private lastEngineError;
24
24
  private lifecycleState;
25
+ private downloadLockHandle;
25
26
  private stopRequested;
26
27
  protected readonly modelsDirectory: string;
27
28
  constructor({ contextLength, engineConfig, enginePort, engineType, logger, model, root }: {
@@ -52,10 +53,8 @@ export declare class ModelManager extends EventEmitter<ModelManagerEvents> {
52
53
  private startHealthPoll;
53
54
  private get lockFilePath();
54
55
  private acquireDownloadLock;
55
- private getLockAge;
56
56
  private recordEngineError;
57
57
  private releaseDownloadLock;
58
- private waitForDownloadLock;
59
58
  private bindEngineProcessEvents;
60
59
  private startEngineProcess;
61
60
  }
@@ -0,0 +1,32 @@
1
+ interface LockLogger {
2
+ info(message: string, attributes?: Record<string, unknown>): void;
3
+ warn(message: string, attributes?: Record<string, unknown>): void;
4
+ }
5
+ export interface DownloadLockHandle {
6
+ lockFilePath: string;
7
+ token: string;
8
+ }
9
+ /**
10
+ * Acquire an exclusive cross-process file lock. Creation uses the "wx" flag
11
+ * so the check-and-create step is atomic: concurrent processes can never both
12
+ * observe a missing lock file and both claim it. Each acquisition owns a
13
+ * unique token; only a lock whose stored token matches the holder's handle
14
+ * may be released by that holder. An active (non-stale) lock is never broken
15
+ * because a waiter timed out - the waiter fails instead.
16
+ *
17
+ * Staleness is judged per observed instance: a parsed lock is stale when its
18
+ * recorded acquisition age exceeds staleMs; an unparseable lock (possible
19
+ * mid-write observation) is stale only when its mtime is itself old, so a
20
+ * fresh lock can never be quarantined off the back of a partial read.
21
+ */
22
+ export declare function acquireExclusiveFileLock({ lockFilePath, logger, pollIntervalMs, staleMs, timeoutMs }: {
23
+ lockFilePath: string;
24
+ logger: LockLogger;
25
+ pollIntervalMs: number;
26
+ staleMs: number;
27
+ timeoutMs: number;
28
+ }): Promise<DownloadLockHandle>;
29
+ export declare function releaseExclusiveFileLock({ handle }: {
30
+ handle: DownloadLockHandle;
31
+ }): Promise<void>;
32
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@infersec/conduit",
3
3
  "description": "End user conduit agent for connecting local LLMs to the cloud.",
4
- "version": "1.106.0",
4
+ "version": "1.108.0",
5
5
  "bin": {
6
6
  "infersec-conduit": "./dist/cli.js"
7
7
  },