@infersec/conduit 1.46.0 → 1.47.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
@@ -18502,22 +18502,6 @@ const ULIDSchema = string$1().refine(isValid, { message: "Invalid ULID" });
18502
18502
  //
18503
18503
  // irid://SERVICE/ACCOUNT_ID/RESOURCE_ID::RESOURCE_SUB_SPECIFIER
18504
18504
  const IRID_MAX_LENGTH = 2048;
18505
- // export const IRIDSchema = z.custom<`irid://${string}/${string}/${string}:${string}`>((irid) => {
18506
- // if (typeof irid !== "string") return false;
18507
- // if (!/^irid:\/\//.test(irid)) return false;
18508
- // const prefixStripped = irid.replace(/^irid:\/\//, "");
18509
- // if (irid.length > IRID_MAX_LENGTH) {
18510
- // return false;
18511
- // }
18512
- // if (!/:[a-z0-9:.-]+$/.test(prefixStripped)) return false;
18513
- // const [components] = prefixStripped.split(":");
18514
- // const [service, accountID, resourceID] = components.split("/");
18515
- // if (!/^[a-z0-9-]+$/.test(service)) return false;
18516
- // if (!isValid(accountID)) return false;
18517
- // if (!/^[a-z0-9-]+$/.test(resourceID)) return false;
18518
- // return true;
18519
- // }, "Invalid IRID value");
18520
- // export const IRIDSchema = z.string().brand<`irid://${string}/${string}/${string}:${string}`>();
18521
18505
  const IRIDSchema = string$1()
18522
18506
  .refine(irid => /^irid:\/\//.test(irid), { message: "Bad IRID prefix" })
18523
18507
  .refine(irid => irid.length <= IRID_MAX_LENGTH, {
@@ -18965,6 +18949,7 @@ const ConduitStateSchema = z
18965
18949
  })
18966
18950
  ])
18967
18951
  .and(z.object({
18952
+ activeRequestCount: z.number().int().nonnegative().optional(),
18968
18953
  timestamp: z.iso.datetime()
18969
18954
  }));
18970
18955
  const ConduitState = z.preprocess(value => {
@@ -19666,6 +19651,17 @@ const API_CLIENT_CONDUIT_OPENAI_REFERENCE = {
19666
19651
  }
19667
19652
  });
19668
19653
 
19654
+ var RoutingMethod;
19655
+ (function (RoutingMethod) {
19656
+ RoutingMethod["FirstAvailable"] = "first-available";
19657
+ RoutingMethod["RoundRobin"] = "round-robin";
19658
+ })(RoutingMethod || (RoutingMethod = {}));
19659
+ RoutingMethod.FirstAvailable;
19660
+ ({
19661
+ [RoutingMethod.FirstAvailable]: "First Available",
19662
+ [RoutingMethod.RoundRobin]: "Round Robin"
19663
+ });
19664
+
19669
19665
  object({
19670
19666
  accountID: ULIDSchema.optional(),
19671
19667
  email: string$1().email(),
@@ -19741,7 +19737,7 @@ custom((perm) => {
19741
19737
  });
19742
19738
  // #endregion
19743
19739
 
19744
- // @ts-ignore
19740
+ // @ts-expect-error Cannot find name "window" in VSCode
19745
19741
  const isBrowser = typeof window !== "undefined";
19746
19742
 
19747
19743
  const APIRequestSchema = object({
@@ -112782,6 +112778,7 @@ class ModelManager extends EventEmitter {
112782
112778
  contextLength;
112783
112779
  logger;
112784
112780
  engineProcess = null;
112781
+ healthPollInterval = null;
112785
112782
  lifecycleState = "stopped";
112786
112783
  stopRequested = false;
112787
112784
  modelsDirectory;
@@ -112919,6 +112916,9 @@ class ModelManager extends EventEmitter {
112919
112916
  }
112920
112917
  this.lifecycleState = "errored";
112921
112918
  this.emit("engineError", err);
112919
+ if (this.engineProcess) {
112920
+ this.startHealthPoll();
112921
+ }
112922
112922
  throw err;
112923
112923
  }
112924
112924
  this.lifecycleState = "running";
@@ -112930,16 +112930,18 @@ class ModelManager extends EventEmitter {
112930
112930
  message: "Cannot stop LLM engine: already stopping"
112931
112931
  });
112932
112932
  }
112933
- if (this.lifecycleState !== "running" && this.lifecycleState !== "starting") {
112933
+ if (this.lifecycleState !== "running" &&
112934
+ this.lifecycleState !== "starting" &&
112935
+ this.lifecycleState !== "errored") {
112934
112936
  throw new UnknownError({
112935
112937
  message: `Cannot stop LLM engine: Invalid state: ${this.lifecycleState}`
112936
112938
  });
112937
112939
  }
112940
+ this.clearHealthPoll();
112938
112941
  const processManager = this.engineProcess;
112939
112942
  if (!processManager) {
112940
- throw new UnknownError({
112941
- message: "Cannot stop LLM engine: engine process not running"
112942
- });
112943
+ this.lifecycleState = "stopped";
112944
+ return;
112943
112945
  }
112944
112946
  this.lifecycleState = "stopping";
112945
112947
  this.stopRequested = true;
@@ -112949,32 +112951,63 @@ class ModelManager extends EventEmitter {
112949
112951
  return this.lifecycleState === "stopped";
112950
112952
  }
112951
112953
  get canStop() {
112952
- return this.lifecycleState === "running" || this.lifecycleState === "starting";
112954
+ return (this.lifecycleState === "running" ||
112955
+ this.lifecycleState === "starting" ||
112956
+ this.lifecycleState === "errored");
112953
112957
  }
112954
112958
  get state() {
112955
112959
  return this.lifecycleState;
112956
112960
  }
112957
- async isEngineReady() {
112961
+ async checkEngineReadiness() {
112958
112962
  switch (this.engine) {
112959
- case "llama.cpp":
112963
+ case "llama.cpp": {
112964
+ return this.checkLlamacppReadiness();
112965
+ }
112960
112966
  case "vllm": {
112961
- try {
112962
- const response = await this.fetchOpenAI("/v1/models", {
112963
- method: "GET",
112964
- signal: AbortSignal.timeout(5000)
112965
- });
112966
- return response.ok;
112967
- }
112968
- catch (_error) {
112969
- return false;
112970
- }
112967
+ return this.checkVLLMReadiness();
112971
112968
  }
112972
112969
  default:
112973
- return true;
112970
+ return "ready";
112971
+ }
112972
+ }
112973
+ async checkLlamacppReadiness() {
112974
+ try {
112975
+ const response = await undiciExports.fetch(joinURL(`http://localhost:${this.enginePort}`, "/health"), {
112976
+ method: "GET",
112977
+ signal: AbortSignal.timeout(5000)
112978
+ });
112979
+ if (response.status === 503) {
112980
+ return "loading";
112981
+ }
112982
+ if (response.ok) {
112983
+ return "ready";
112984
+ }
112985
+ return "loading";
112986
+ }
112987
+ catch (_error) {
112988
+ return "unreachable";
112989
+ }
112990
+ }
112991
+ async checkVLLMReadiness() {
112992
+ try {
112993
+ const response = await undiciExports.fetch(joinURL(`http://localhost:${this.enginePort}`, "/v1/models"), {
112994
+ method: "GET",
112995
+ signal: AbortSignal.timeout(5000)
112996
+ });
112997
+ if (response.status === 503) {
112998
+ return "loading";
112999
+ }
113000
+ if (response.ok) {
113001
+ return "ready";
113002
+ }
113003
+ return "loading";
113004
+ }
113005
+ catch (_error) {
113006
+ return "unreachable";
112974
113007
  }
112975
113008
  }
112976
113009
  async waitForEngineReady() {
112977
- const maxWaitMs = 5 * 60 * 1000;
113010
+ const maxWaitMs = 15 * 60 * 1000;
112978
113011
  const pollIntervalMs = 2000;
112979
113012
  const start = Date.now();
112980
113013
  while (Date.now() - start < maxWaitMs) {
@@ -112984,14 +113017,41 @@ class ModelManager extends EventEmitter {
112984
113017
  if (!this.engineProcess) {
112985
113018
  throw new Error("LLM engine process exited before readiness checks completed");
112986
113019
  }
112987
- const ready = await this.isEngineReady();
112988
- if (ready) {
113020
+ const readiness = await this.checkEngineReadiness();
113021
+ if (readiness === "ready") {
112989
113022
  return;
112990
113023
  }
112991
113024
  await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
112992
113025
  }
112993
113026
  throw new Error("LLM engine failed readiness checks within timeout");
112994
113027
  }
113028
+ clearHealthPoll() {
113029
+ if (this.healthPollInterval) {
113030
+ clearInterval(this.healthPollInterval);
113031
+ this.healthPollInterval = null;
113032
+ }
113033
+ }
113034
+ startHealthPoll() {
113035
+ this.clearHealthPoll();
113036
+ this.logger.info("Starting background health poll for errored engine");
113037
+ this.healthPollInterval = setInterval(() => {
113038
+ if (!this.engineProcess) {
113039
+ this.clearHealthPoll();
113040
+ return;
113041
+ }
113042
+ this.checkEngineReadiness()
113043
+ .then(readiness => {
113044
+ if (readiness === "ready") {
113045
+ this.clearHealthPoll();
113046
+ this.lifecycleState = "running";
113047
+ this.emit("engineReady");
113048
+ }
113049
+ })
113050
+ .catch(() => {
113051
+ // keep polling
113052
+ });
113053
+ }, 15_000);
113054
+ }
112995
113055
  bindEngineProcessEvents(processManager) {
112996
113056
  let hasTerminated = false;
112997
113057
  processManager.on("stderr", line => {
@@ -113005,6 +113065,7 @@ class ModelManager extends EventEmitter {
113005
113065
  return;
113006
113066
  }
113007
113067
  hasTerminated = true;
113068
+ this.clearHealthPoll();
113008
113069
  if (this.stopRequested) {
113009
113070
  this.engineProcess = null;
113010
113071
  this.lifecycleState = "stopped";
@@ -113026,6 +113087,7 @@ class ModelManager extends EventEmitter {
113026
113087
  return;
113027
113088
  }
113028
113089
  hasTerminated = true;
113090
+ this.clearHealthPoll();
113029
113091
  this.engineProcess = null;
113030
113092
  if (this.stopRequested) {
113031
113093
  this.lifecycleState = "stopped";
@@ -114844,6 +114906,7 @@ class ConduitStateReportManager {
114844
114906
  }
114845
114907
 
114846
114908
  class ConduitStateManager {
114909
+ activeRequestCount = 0;
114847
114910
  currentState;
114848
114911
  constructor({ initialState }) {
114849
114912
  this.currentState = {
@@ -114851,22 +114914,31 @@ class ConduitStateManager {
114851
114914
  timestamp: new Date().toISOString()
114852
114915
  };
114853
114916
  }
114917
+ decrementActiveRequestCount() {
114918
+ this.activeRequestCount = Math.max(0, this.activeRequestCount - 1);
114919
+ }
114854
114920
  getState() {
114855
- return this.currentState;
114921
+ return {
114922
+ ...this.currentState,
114923
+ activeRequestCount: this.activeRequestCount
114924
+ };
114925
+ }
114926
+ incrementActiveRequestCount() {
114927
+ this.activeRequestCount += 1;
114856
114928
  }
114857
114929
  setState(state) {
114858
114930
  this.currentState = {
114859
114931
  ...state,
114860
114932
  timestamp: new Date().toISOString()
114861
114933
  };
114862
- return this.currentState;
114934
+ return this.getState();
114863
114935
  }
114864
114936
  touch() {
114865
114937
  this.currentState = {
114866
114938
  ...this.currentState,
114867
114939
  timestamp: new Date().toISOString()
114868
114940
  };
114869
- return this.currentState;
114941
+ return this.getState();
114870
114942
  }
114871
114943
  }
114872
114944
 
@@ -124642,10 +124714,12 @@ async function createApplication({ abortController, apiClient, configuration, lo
124642
124714
  });
124643
124715
  },
124644
124716
  onRequestEnd: () => {
124645
- return;
124717
+ conduitStateManager.decrementActiveRequestCount();
124718
+ conduitStateReportManager.reportStateChange();
124646
124719
  },
124647
124720
  onRequestStart: () => {
124648
- return;
124721
+ conduitStateManager.incrementActiveRequestCount();
124722
+ conduitStateReportManager.reportStateChange();
124649
124723
  },
124650
124724
  reportMetrics: apiClient.reportPromptMetrics,
124651
124725
  signal: abortController.signal
@@ -18,6 +18,7 @@ export declare class ModelManager extends EventEmitter<ModelManagerEvents> {
18
18
  readonly contextLength: number | null;
19
19
  protected readonly logger: Logger;
20
20
  private engineProcess;
21
+ private healthPollInterval;
21
22
  private lifecycleState;
22
23
  private stopRequested;
23
24
  protected readonly modelsDirectory: string;
@@ -39,8 +40,12 @@ export declare class ModelManager extends EventEmitter<ModelManagerEvents> {
39
40
  get canStart(): boolean;
40
41
  get canStop(): boolean;
41
42
  get state(): EngineLifecycleState;
42
- private isEngineReady;
43
+ private checkEngineReadiness;
44
+ private checkLlamacppReadiness;
45
+ private checkVLLMReadiness;
43
46
  private waitForEngineReady;
47
+ private clearHealthPoll;
48
+ private startHealthPoll;
44
49
  private bindEngineProcessEvents;
45
50
  private startEngineProcess;
46
51
  }
@@ -3,11 +3,14 @@ type ConduitStateInput = Record<string, unknown> & {
3
3
  state: ConduitState["state"];
4
4
  };
5
5
  export declare class ConduitStateManager {
6
+ private activeRequestCount;
6
7
  private currentState;
7
8
  constructor({ initialState }: {
8
9
  initialState: ConduitStateInput;
9
10
  });
11
+ decrementActiveRequestCount(): void;
10
12
  getState(): ConduitState;
13
+ incrementActiveRequestCount(): void;
11
14
  setState(state: ConduitStateInput): ConduitState;
12
15
  touch(): ConduitState;
13
16
  }
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.46.0",
4
+ "version": "1.47.0",
5
5
  "bin": {
6
6
  "infersec-conduit": "./dist/cli.js"
7
7
  },
@@ -22,6 +22,8 @@
22
22
  "format": "prettier --write .",
23
23
  "prepublishOnly": "npm run build",
24
24
  "start": "npm run build && node ./dist/cli.js inference start",
25
+ "start-2": "npm run build && node ./dist/cli.js inference start --source 01k1p3rgt1wdq8k43rev1dg4mh --port 9506",
26
+ "start-3": "npm run build && node ./dist/cli.js inference start --source 01k1p3rgt1wdq8k43rev1dg4mj --port 9507",
25
27
  "test": "npm run test:types && npm run test:lint && npm run test:format && npm run test:unit",
26
28
  "test:format": "prettier --check .",
27
29
  "test:lint": "eslint source/**/*.ts",