@infersec/conduit 1.6.3 → 1.6.4

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
@@ -6,7 +6,7 @@ const __dirname = __pathDirname(__filename);
6
6
 
7
7
  import { parseArgs } from 'node:util';
8
8
  import 'node:crypto';
9
- import { a as asError, s as startInferenceAgent } from './start-CQayYNYZ.js';
9
+ import { a as asError, s as startInferenceAgent } from './start-D9p7R0lT.js';
10
10
  import 'argon2';
11
11
  import 'node:child_process';
12
12
  import 'node:stream';
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ const __filename = __fileURLToPath(import.meta.url);
5
5
  const __dirname = __pathDirname(__filename);
6
6
 
7
7
  import 'node:crypto';
8
- import { s as startInferenceAgent, a as asError } from './start-CQayYNYZ.js';
8
+ import { s as startInferenceAgent, a as asError } from './start-D9p7R0lT.js';
9
9
  import 'argon2';
10
10
  import 'node:child_process';
11
11
  import 'node:stream';
@@ -2,6 +2,7 @@ import { LLMEngine, LLMModel } from "@infersec/definitions";
2
2
  import { Logger } from "@infersec/logger";
3
3
  import EventEmitter from "eventemitter3";
4
4
  import { RequestInit, Response } from "undici";
5
+ import { type ModelDownloadProgressUpdate } from "./download.js";
5
6
  interface ModelManagerEvents {
6
7
  engineError: (error: Error) => void;
7
8
  engineReady: () => void;
@@ -25,7 +26,9 @@ export declare class ModelManager extends EventEmitter<ModelManagerEvents> {
25
26
  root: string;
26
27
  });
27
28
  fetchOpenAI(path: string, opts?: RequestInit): Promise<Response>;
28
- prepare(): Promise<void>;
29
+ prepare({ onDownloadProgress }?: {
30
+ onDownloadProgress?: (update: ModelDownloadProgressUpdate) => void;
31
+ }): Promise<void>;
29
32
  start(): Promise<void>;
30
33
  }
31
34
  export {};
@@ -1,8 +1,20 @@
1
1
  import { LLMModel } from "@infersec/definitions";
2
- export declare function downloadModelViaHuggingFace({ format, huggingFaceToken, modelSlug: rawModelSlug, progressFilePath, targetDirectory }: {
2
+ export type ModelDownloadProgressUpdate = {
3
+ file: {
4
+ bytes: number;
5
+ name: string;
6
+ total: number;
7
+ };
8
+ total: {
9
+ completed: number;
10
+ total: number;
11
+ };
12
+ };
13
+ export declare function downloadModelViaHuggingFace({ format, huggingFaceToken, modelSlug: rawModelSlug, onProgress, progressFilePath, targetDirectory }: {
3
14
  format: LLMModel["format"];
4
15
  huggingFaceToken: string | null | undefined;
5
16
  modelSlug: string;
17
+ onProgress?: (update: ModelDownloadProgressUpdate) => void;
6
18
  progressFilePath: string;
7
19
  targetDirectory: string;
8
20
  }): Promise<void>;
@@ -1,9 +1,11 @@
1
1
  import { type APIResponse, type ServerToClientAPIRequest } from "@infersec/definitions";
2
2
  import { Logger } from "@infersec/logger";
3
3
  import { Configuration } from "../configuration.js";
4
- export declare function handleSSERequests({ apiURL, configuration, logger, onRequest }: {
4
+ export declare function handleSSERequests({ apiURL, configuration, logger, onRequest, onRequestEnd, onRequestStart }: {
5
5
  apiURL: string;
6
6
  configuration: Configuration;
7
7
  logger: Logger;
8
8
  onRequest: (request: ServerToClientAPIRequest) => Promise<APIResponse>;
9
+ onRequestEnd?: (request: ServerToClientAPIRequest) => Promise<void> | void;
10
+ onRequestStart?: (request: ServerToClientAPIRequest) => Promise<void> | void;
9
11
  }): Promise<void>;
@@ -96476,7 +96476,7 @@ const ModelDownloadProgressSchema = object({
96476
96476
  const DOWNLOAD_PROGRESS_TIMEOUT = 60000;
96477
96477
  const DOWNLOAD_RETRY_ATTEMPTS_FULL = 3;
96478
96478
  const DOWNLOAD_RETRY_ATTEMPTS_RANGE = 10;
96479
- async function downloadModelViaHuggingFace({ format, huggingFaceToken, modelSlug: rawModelSlug, progressFilePath, targetDirectory }) {
96479
+ async function downloadModelViaHuggingFace({ format, huggingFaceToken, modelSlug: rawModelSlug, onProgress, progressFilePath, targetDirectory }) {
96480
96480
  // Sanitise model ID
96481
96481
  const [modelSlugWithRevision, variant = null] = rawModelSlug.split(":");
96482
96482
  const { modelSlug, revision } = parseModelRevision(modelSlugWithRevision);
@@ -96494,6 +96494,7 @@ async function downloadModelViaHuggingFace({ format, huggingFaceToken, modelSlug
96494
96494
  }
96495
96495
  console.log("\n\n\nFORMAT", { format, variant });
96496
96496
  const accessToken = huggingFaceToken ?? undefined;
96497
+ const allFiles = [];
96497
96498
  // Get all files in model
96498
96499
  for await (const file of listFiles({
96499
96500
  accessToken,
@@ -96510,7 +96511,31 @@ async function downloadModelViaHuggingFace({ format, huggingFaceToken, modelSlug
96510
96511
  console.log("Skipping due variant not being needed:", file.path, file.size);
96511
96512
  continue;
96512
96513
  }
96513
- if (progress.completedFiles.includes(file.path)) {
96514
+ allFiles.push({
96515
+ path: file.path,
96516
+ size: file.size
96517
+ });
96518
+ }
96519
+ const completedSet = new Set(progress.completedFiles);
96520
+ const totalFiles = allFiles.length;
96521
+ let completedFiles = allFiles.filter(file => completedSet.has(file.path)).length;
96522
+ const reportProgress = (update) => {
96523
+ if (!onProgress)
96524
+ return;
96525
+ onProgress({
96526
+ file: {
96527
+ bytes: update.bytes,
96528
+ name: update.name,
96529
+ total: update.total
96530
+ },
96531
+ total: {
96532
+ completed: completedFiles,
96533
+ total: totalFiles
96534
+ }
96535
+ });
96536
+ };
96537
+ for (const file of allFiles) {
96538
+ if (completedSet.has(file.path)) {
96514
96539
  console.log("Skipping due to already having completed file:", file.path, file.size);
96515
96540
  continue;
96516
96541
  }
@@ -96522,14 +96547,23 @@ async function downloadModelViaHuggingFace({ format, huggingFaceToken, modelSlug
96522
96547
  revision
96523
96548
  });
96524
96549
  const { partialPath } = getDownloadPaths({ filePath: file.path, targetDirectory });
96550
+ const fileTotalSize = rangeInfo.totalSize ?? file.size;
96551
+ reportProgress({ bytes: 0, name: file.path, total: fileTotalSize });
96525
96552
  const shouldAttemptRange = rangeInfo.supportsRanges || existsSync(partialPath);
96526
96553
  if (shouldAttemptRange) {
96527
96554
  try {
96528
96555
  await downloadFileWithRange({
96529
96556
  accessToken,
96530
96557
  filePath: file.path,
96531
- fileSize: rangeInfo.totalSize ?? file.size,
96558
+ fileSize: fileTotalSize,
96532
96559
  modelSlug,
96560
+ onProgress: progressUpdate => {
96561
+ reportProgress({
96562
+ bytes: progressUpdate.bytes,
96563
+ name: file.path,
96564
+ total: progressUpdate.total
96565
+ });
96566
+ },
96533
96567
  revision,
96534
96568
  targetDirectory
96535
96569
  });
@@ -96540,8 +96574,15 @@ async function downloadModelViaHuggingFace({ format, huggingFaceToken, modelSlug
96540
96574
  await downloadFileFull({
96541
96575
  accessToken,
96542
96576
  filePath: file.path,
96543
- fileSize: file.size,
96577
+ fileSize: fileTotalSize,
96544
96578
  modelSlug,
96579
+ onProgress: progressUpdate => {
96580
+ reportProgress({
96581
+ bytes: progressUpdate.bytes,
96582
+ name: file.path,
96583
+ total: progressUpdate.total
96584
+ });
96585
+ },
96545
96586
  targetDirectory
96546
96587
  });
96547
96588
  }
@@ -96557,13 +96598,22 @@ async function downloadModelViaHuggingFace({ format, huggingFaceToken, modelSlug
96557
96598
  await downloadFileFull({
96558
96599
  accessToken,
96559
96600
  filePath: file.path,
96560
- fileSize: file.size,
96601
+ fileSize: fileTotalSize,
96561
96602
  modelSlug,
96603
+ onProgress: progressUpdate => {
96604
+ reportProgress({
96605
+ bytes: progressUpdate.bytes,
96606
+ name: file.path,
96607
+ total: progressUpdate.total
96608
+ });
96609
+ },
96562
96610
  targetDirectory
96563
96611
  });
96564
96612
  }
96565
96613
  // Update progress
96566
96614
  progress.completedFiles.push(file.path);
96615
+ completedFiles += 1;
96616
+ reportProgress({ bytes: fileTotalSize, name: file.path, total: fileTotalSize });
96567
96617
  await writeFile(progressFilePath, JSON.stringify(progress, undefined, 4));
96568
96618
  }
96569
96619
  }
@@ -96749,7 +96799,7 @@ async function getRangeInfo({ accessToken, filePath, fileSize, modelSlug, revisi
96749
96799
  };
96750
96800
  }
96751
96801
  }
96752
- async function downloadFileFull({ accessToken, filePath, fileSize, modelSlug, targetDirectory }) {
96802
+ async function downloadFileFull({ accessToken, filePath, fileSize, modelSlug, onProgress, targetDirectory }) {
96753
96803
  const { finalPath, partialPath } = getDownloadPaths({ filePath, targetDirectory });
96754
96804
  let lastError = null;
96755
96805
  for (let attempt = 1; attempt <= DOWNLOAD_RETRY_ATTEMPTS_FULL; attempt++) {
@@ -96781,6 +96831,7 @@ async function downloadFileFull({ accessToken, filePath, fileSize, modelSlug, ta
96781
96831
  };
96782
96832
  input.on("data", resetProgressTimeout);
96783
96833
  resetProgressTimeout();
96834
+ onProgress?.({ bytes: 0, total: fileSize });
96784
96835
  meter.progress.on("progress", (totalBytes) => {
96785
96836
  lastProgressBytes = totalBytes;
96786
96837
  const percentComplete = ((totalBytes / fileSize) * 100).toFixed(1);
@@ -96788,6 +96839,7 @@ async function downloadFileFull({ accessToken, filePath, fileSize, modelSlug, ta
96788
96839
  console.log(` => ${percentComplete}% (${totalBytes} / ${fileSize})`);
96789
96840
  }
96790
96841
  lastPercentage = percentComplete;
96842
+ onProgress?.({ bytes: totalBytes, total: fileSize });
96791
96843
  });
96792
96844
  try {
96793
96845
  await pipeline(input, meter, output);
@@ -96809,7 +96861,7 @@ async function downloadFileFull({ accessToken, filePath, fileSize, modelSlug, ta
96809
96861
  const errorMessage = `Failed downloading ${filePath} (${fileSize} bytes) after ${DOWNLOAD_RETRY_ATTEMPTS_FULL} attempts. Last error: ${lastError?.message ?? "Unknown error"}`;
96810
96862
  throw new Error(errorMessage);
96811
96863
  }
96812
- async function downloadFileWithRange({ accessToken, filePath, fileSize, modelSlug, revision, targetDirectory }) {
96864
+ async function downloadFileWithRange({ accessToken, filePath, fileSize, modelSlug, onProgress, revision, targetDirectory }) {
96813
96865
  const { finalPath, partialPath } = getDownloadPaths({ filePath, targetDirectory });
96814
96866
  let lastError = null;
96815
96867
  for (let attempt = 1; attempt <= DOWNLOAD_RETRY_ATTEMPTS_RANGE; attempt++) {
@@ -96826,6 +96878,7 @@ async function downloadFileWithRange({ accessToken, filePath, fileSize, modelSlu
96826
96878
  return;
96827
96879
  }
96828
96880
  }
96881
+ onProgress?.({ bytes: startOffset, total: fileSize });
96829
96882
  try {
96830
96883
  const resumeLabel = startOffset > 0 ? `resuming at ${startOffset}` : "starting";
96831
96884
  console.log("Downloading:", filePath, fileSize, `(attempt ${attempt}, ${resumeLabel})`);
@@ -96891,6 +96944,7 @@ async function downloadFileWithRange({ accessToken, filePath, fileSize, modelSlu
96891
96944
  console.log(` => ${percentComplete}% (${lastProgressBytes} / ${fileSize})`);
96892
96945
  }
96893
96946
  lastPercentage = percentComplete;
96947
+ onProgress?.({ bytes: lastProgressBytes, total: fileSize });
96894
96948
  });
96895
96949
  try {
96896
96950
  await pipeline(input, meter, output);
@@ -104952,7 +105006,7 @@ class ModelManager extends EventEmitter {
104952
105006
  }
104953
105007
  }
104954
105008
  }
104955
- async prepare() {
105009
+ async prepare({ onDownloadProgress } = {}) {
104956
105010
  this.logger.info("Preparing LLM engine", {
104957
105011
  agentEngineType: this.engine,
104958
105012
  modelID: this.model.id
@@ -104967,6 +105021,7 @@ class ModelManager extends EventEmitter {
104967
105021
  format: this.model.format,
104968
105022
  huggingFaceToken: this.model.source.modelSecret,
104969
105023
  modelSlug: this.model.source.slug,
105024
+ onProgress: onDownloadProgress,
104970
105025
  progressFilePath: join(this.modelsDirectory, `${this.uniqueName}.progress.json`),
104971
105026
  targetDirectory: join(this.modelsDirectory, this.uniqueName)
104972
105027
  });
@@ -105034,7 +105089,7 @@ class ModelManager extends EventEmitter {
105034
105089
  }
105035
105090
  }
105036
105091
 
105037
- async function handleSSERequests({ apiURL, configuration, logger, onRequest }) {
105092
+ async function handleSSERequests({ apiURL, configuration, logger, onRequest, onRequestEnd, onRequestStart }) {
105038
105093
  const streamURL = `${apiURL}/conduit/api/v1/source/${configuration.inferenceSourceID}/requests/stream`;
105039
105094
  await connectSSE(streamURL, {
105040
105095
  headers: {
@@ -105054,6 +105109,8 @@ async function handleSSERequests({ apiURL, configuration, logger, onRequest }) {
105054
105109
  apiURL,
105055
105110
  configuration,
105056
105111
  logger,
105112
+ onRequestEnd,
105113
+ onRequestStart,
105057
105114
  onRequest,
105058
105115
  request: payload
105059
105116
  }).catch(error => {
@@ -105065,8 +105122,9 @@ async function handleSSERequests({ apiURL, configuration, logger, onRequest }) {
105065
105122
  }
105066
105123
  });
105067
105124
  }
105068
- async function handleRequest({ apiURL, configuration, logger, onRequest, request }) {
105125
+ async function handleRequest({ apiURL, configuration, logger, onRequest, onRequestEnd, onRequestStart, request }) {
105069
105126
  try {
105127
+ await onRequestStart?.(request);
105070
105128
  const response = await onRequest(request);
105071
105129
  await streamResponse({
105072
105130
  apiURL,
@@ -105102,6 +105160,9 @@ async function handleRequest({ apiURL, configuration, logger, onRequest, request
105102
105160
  requestID: request.requestID
105103
105161
  });
105104
105162
  }
105163
+ finally {
105164
+ await onRequestEnd?.(request);
105165
+ }
105105
105166
  }
105106
105167
  async function streamResponse({ apiURL, configuration, logger, requestID, response }) {
105107
105168
  let sequence = 0;
@@ -114715,6 +114776,9 @@ async function createApplication({ abortController, apiClient, configuration, lo
114715
114776
  state: "initialising"
114716
114777
  }
114717
114778
  });
114779
+ const modelFileName = getConduitModelFileName(conduitConfiguration);
114780
+ const modelName = getConduitModelName(conduitConfiguration);
114781
+ const idleReason = "Awaiting requests";
114718
114782
  const startup = Date.now();
114719
114783
  // Initialise model manager
114720
114784
  const modelManager = new ModelManager({
@@ -114743,20 +114807,44 @@ async function createApplication({ abortController, apiClient, configuration, lo
114743
114807
  });
114744
114808
  modelManager.on("engineReady", () => {
114745
114809
  conduitStateManager.setState({
114746
- modelName: getConduitModelName(conduitConfiguration),
114747
- state: "online"
114810
+ reason: idleReason,
114811
+ state: "idle"
114748
114812
  });
114749
114813
  });
114750
114814
  conduitStateManager.setState({
114751
- modelFileName: getConduitModelFileName(conduitConfiguration),
114752
- modelName: getConduitModelName(conduitConfiguration),
114815
+ modelFileName,
114816
+ modelName,
114753
114817
  state: "downloadingModelFiles",
114754
114818
  totalProgress: {
114755
114819
  file: 0,
114756
114820
  total: 0
114757
114821
  }
114758
114822
  });
114759
- await modelManager.prepare();
114823
+ let lastDownloadKey = "";
114824
+ const reportDownloadProgress = (update) => {
114825
+ const filePercent = update.file.total > 0 ? Math.floor((update.file.bytes / update.file.total) * 100) : 0;
114826
+ const downloadKey = `${update.total.completed}/${update.total.total}:${update.file.name}:${filePercent}`;
114827
+ if (downloadKey === lastDownloadKey) {
114828
+ return;
114829
+ }
114830
+ lastDownloadKey = downloadKey;
114831
+ conduitStateManager.setState({
114832
+ fileProgress: {
114833
+ bytes: Math.max(0, Math.floor(update.file.bytes)),
114834
+ total: Math.max(0, Math.floor(update.file.total))
114835
+ },
114836
+ modelFileName: update.file.name,
114837
+ modelName,
114838
+ state: "downloadingModelFiles",
114839
+ totalProgress: {
114840
+ file: Math.max(0, Math.floor(update.total.completed)),
114841
+ total: Math.max(0, Math.floor(update.total.total))
114842
+ }
114843
+ });
114844
+ };
114845
+ await modelManager.prepare({
114846
+ onDownloadProgress: reportDownloadProgress
114847
+ });
114760
114848
  conduitStateManager.setState({
114761
114849
  state: "bootingEngine"
114762
114850
  });
@@ -114836,15 +114924,40 @@ async function createApplication({ abortController, apiClient, configuration, lo
114836
114924
  });
114837
114925
  });
114838
114926
  }, CONDUIT_STATE_INTERVAL_MS);
114927
+ let activeRequests = 0;
114928
+ const setIdleState = () => {
114929
+ conduitStateManager.setState({
114930
+ reason: idleReason,
114931
+ state: "idle"
114932
+ });
114933
+ };
114934
+ const setOnlineState = () => {
114935
+ conduitStateManager.setState({
114936
+ modelName,
114937
+ state: "online"
114938
+ });
114939
+ };
114839
114940
  handleSSERequests({
114840
114941
  apiURL: configuration.apiURL,
114841
114942
  configuration,
114842
114943
  logger,
114944
+ onRequestEnd: () => {
114945
+ activeRequests = Math.max(0, activeRequests - 1);
114946
+ if (activeRequests === 0) {
114947
+ setIdleState();
114948
+ }
114949
+ },
114843
114950
  onRequest: async (request) => {
114844
114951
  return proxyRequest({
114845
114952
  configuration,
114846
114953
  request
114847
114954
  });
114955
+ },
114956
+ onRequestStart: () => {
114957
+ activeRequests += 1;
114958
+ if (activeRequests === 1) {
114959
+ setOnlineState();
114960
+ }
114848
114961
  }
114849
114962
  }).catch(error => {
114850
114963
  logger.error("SSE handler failed", {
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.6.3",
4
+ "version": "1.6.4",
5
5
  "bin": {
6
6
  "infersec-conduit": "./dist/cli.js"
7
7
  },