@infersec/conduit 1.25.2 → 1.26.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/README.md +10 -8
- package/dist/application.d.ts +4 -1
- package/dist/cli.js +8 -1
- package/dist/configuration.d.ts +9 -0
- package/dist/index.js +1 -1
- package/dist/modelManagement/ModelManager.d.ts +9 -0
- package/dist/{start-BJ-o7I20.js → start-PzV0cQI5.js} +447 -138
- package/dist/state/ConduitStateReportManager.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,14 +10,15 @@ npx @infersec/conduit --engine <type> --key <api-key> --source <source-id>
|
|
|
10
10
|
|
|
11
11
|
### Flags
|
|
12
12
|
|
|
13
|
-
| Flag
|
|
14
|
-
|
|
|
15
|
-
| `--engine`
|
|
16
|
-
| `--key`
|
|
17
|
-
| `--source`
|
|
18
|
-
| `--api-url`
|
|
19
|
-
| `--port`
|
|
20
|
-
| `--root`
|
|
13
|
+
| Flag | Required | Default | Notes |
|
|
14
|
+
| -------------- | -------- | ------------------------------ | -------------------------------------------------------------------------------------- |
|
|
15
|
+
| `--engine` | Yes | - | Engine type (matches `ENGINE`). |
|
|
16
|
+
| `--key` | Yes | - | API key (matches `API_KEY`). |
|
|
17
|
+
| `--source` | Yes | - | Inference source ID (matches `SOURCE`). |
|
|
18
|
+
| `--api-url` | No | `https://api.infersec.ai` | API base URL (matches `API_URL`). |
|
|
19
|
+
| `--port` | No | `9505` | Port to listen on (matches `PORT`). |
|
|
20
|
+
| `--root` | No | `$HOME/.cache/infersec/iagent` | Root directory (matches `ROOT_DIRECTORY`). |
|
|
21
|
+
| `--start-mode` | No | `auto` | Startup mode (matches `START_MODE`): `auto` starts engine, `idle` leaves conduit idle. |
|
|
21
22
|
|
|
22
23
|
### Examples
|
|
23
24
|
|
|
@@ -36,5 +37,6 @@ npx @infersec/conduit --engine <type> --key <api-key> --source <source-id>
|
|
|
36
37
|
| `API_URL` | No | `https://api.infersec.ai` | API base URL (matches `--api-url`). |
|
|
37
38
|
| `PORT` | No | `9505` | Port to listen on (matches `--port`). |
|
|
38
39
|
| `ROOT_DIRECTORY` | No | `$HOME/.cache/infersec/iagent` | Root directory (matches `--root`). |
|
|
40
|
+
| `START_MODE` | No | `auto` | Startup mode (matches `--start-mode`). |
|
|
39
41
|
|
|
40
42
|
CLI flags override environment variables when both are provided.
|
package/dist/application.d.ts
CHANGED
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-
|
|
9
|
+
import { a as asError, s as startInferenceAgent } from './start-PzV0cQI5.js';
|
|
10
10
|
import 'argon2';
|
|
11
11
|
import 'node:child_process';
|
|
12
12
|
import 'node:stream';
|
|
@@ -68,6 +68,7 @@ Options:
|
|
|
68
68
|
--key <value> API key (or API_KEY)
|
|
69
69
|
--port <number> Port to listen on (or PORT)
|
|
70
70
|
--root <path> Root directory (or ROOT_DIRECTORY)
|
|
71
|
+
--start-mode <mode> Startup mode: auto|idle (or START_MODE)
|
|
71
72
|
--source <id> Inference source ID (or SOURCE)
|
|
72
73
|
-h, --help Show this help message
|
|
73
74
|
`;
|
|
@@ -94,6 +95,9 @@ async function run() {
|
|
|
94
95
|
root: {
|
|
95
96
|
type: "string"
|
|
96
97
|
},
|
|
98
|
+
"start-mode": {
|
|
99
|
+
type: "string"
|
|
100
|
+
},
|
|
97
101
|
source: {
|
|
98
102
|
type: "string"
|
|
99
103
|
}
|
|
@@ -119,6 +123,9 @@ async function run() {
|
|
|
119
123
|
if (values.root) {
|
|
120
124
|
configurationOverrides.rootDirectory = values.root;
|
|
121
125
|
}
|
|
126
|
+
if (values["start-mode"]) {
|
|
127
|
+
configurationOverrides.startMode = values["start-mode"];
|
|
128
|
+
}
|
|
122
129
|
if (values.port) {
|
|
123
130
|
const port = Number.parseInt(values.port, 10);
|
|
124
131
|
if (Number.isNaN(port)) {
|
package/dist/configuration.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { LLMEngine, ULID } from "@infersec/definitions";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
declare const StartModeSchema: z.ZodEnum<{
|
|
4
|
+
idle: "idle";
|
|
5
|
+
auto: "auto";
|
|
6
|
+
}>;
|
|
7
|
+
export type StartMode = z.infer<typeof StartModeSchema>;
|
|
2
8
|
export interface Configuration {
|
|
3
9
|
agentEngineType: LLMEngine;
|
|
4
10
|
apiKey: string;
|
|
@@ -6,6 +12,7 @@ export interface Configuration {
|
|
|
6
12
|
inferenceSourceID: ULID;
|
|
7
13
|
port: number;
|
|
8
14
|
rootDirectory: string;
|
|
15
|
+
startMode: StartMode;
|
|
9
16
|
}
|
|
10
17
|
export interface ConfigurationOverrides {
|
|
11
18
|
agentEngineType?: string;
|
|
@@ -14,7 +21,9 @@ export interface ConfigurationOverrides {
|
|
|
14
21
|
inferenceSourceID?: ULID;
|
|
15
22
|
port?: number;
|
|
16
23
|
rootDirectory?: string;
|
|
24
|
+
startMode?: string;
|
|
17
25
|
}
|
|
18
26
|
export declare function getConfiguration({ overrides }?: {
|
|
19
27
|
overrides?: ConfigurationOverrides;
|
|
20
28
|
}): Configuration;
|
|
29
|
+
export {};
|
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-
|
|
8
|
+
import { s as startInferenceAgent, a as asError } from './start-PzV0cQI5.js';
|
|
9
9
|
import 'argon2';
|
|
10
10
|
import 'node:child_process';
|
|
11
11
|
import 'node:stream';
|
|
@@ -8,6 +8,7 @@ interface ModelManagerEvents {
|
|
|
8
8
|
engineReady: () => void;
|
|
9
9
|
engineTerminated: () => void;
|
|
10
10
|
}
|
|
11
|
+
type EngineLifecycleState = "errored" | "running" | "starting" | "stopped" | "stopping";
|
|
11
12
|
export declare class ModelManager extends EventEmitter<ModelManagerEvents> {
|
|
12
13
|
readonly engine: LLMEngine;
|
|
13
14
|
readonly model: LLMModel;
|
|
@@ -16,6 +17,8 @@ export declare class ModelManager extends EventEmitter<ModelManagerEvents> {
|
|
|
16
17
|
readonly contextLength: number | null;
|
|
17
18
|
protected readonly logger: Logger;
|
|
18
19
|
private engineProcess;
|
|
20
|
+
private lifecycleState;
|
|
21
|
+
private stopRequested;
|
|
19
22
|
protected readonly modelsDirectory: string;
|
|
20
23
|
constructor({ contextLength, engine, logger, model, parallelism, root }: {
|
|
21
24
|
contextLength?: number | null;
|
|
@@ -30,7 +33,13 @@ export declare class ModelManager extends EventEmitter<ModelManagerEvents> {
|
|
|
30
33
|
onDownloadProgress?: (update: ModelDownloadProgressUpdate) => void;
|
|
31
34
|
}): Promise<void>;
|
|
32
35
|
start(): Promise<void>;
|
|
36
|
+
stop(): Promise<void>;
|
|
37
|
+
get canStart(): boolean;
|
|
38
|
+
get canStop(): boolean;
|
|
39
|
+
get state(): EngineLifecycleState;
|
|
33
40
|
private isEngineReady;
|
|
34
41
|
private waitForEngineReady;
|
|
42
|
+
private bindEngineProcessEvents;
|
|
43
|
+
private startEngineProcess;
|
|
35
44
|
}
|
|
36
45
|
export {};
|
|
@@ -14797,6 +14797,9 @@ const ConduitStateSchema = z
|
|
|
14797
14797
|
z.object({
|
|
14798
14798
|
state: z.literal("bootingEngine")
|
|
14799
14799
|
}),
|
|
14800
|
+
z.object({
|
|
14801
|
+
state: z.literal("stoppingEngine")
|
|
14802
|
+
}),
|
|
14800
14803
|
z.object({
|
|
14801
14804
|
state: z.literal("offline")
|
|
14802
14805
|
}),
|
|
@@ -14897,6 +14900,38 @@ const API_SERVICE_CONDUIT_API_REFERENCE = {
|
|
|
14897
14900
|
}
|
|
14898
14901
|
}
|
|
14899
14902
|
},
|
|
14903
|
+
"/conduit/api/v1/source/:sourceID/engine/start": {
|
|
14904
|
+
POST: {
|
|
14905
|
+
auth: {
|
|
14906
|
+
type: "api-key"
|
|
14907
|
+
},
|
|
14908
|
+
parameters: {
|
|
14909
|
+
sourceID: ULIDSchema
|
|
14910
|
+
},
|
|
14911
|
+
response: {
|
|
14912
|
+
schema: object({
|
|
14913
|
+
acknowledged: literal(true)
|
|
14914
|
+
}),
|
|
14915
|
+
type: "rest"
|
|
14916
|
+
}
|
|
14917
|
+
}
|
|
14918
|
+
},
|
|
14919
|
+
"/conduit/api/v1/source/:sourceID/engine/stop": {
|
|
14920
|
+
POST: {
|
|
14921
|
+
auth: {
|
|
14922
|
+
type: "api-key"
|
|
14923
|
+
},
|
|
14924
|
+
parameters: {
|
|
14925
|
+
sourceID: ULIDSchema
|
|
14926
|
+
},
|
|
14927
|
+
response: {
|
|
14928
|
+
schema: object({
|
|
14929
|
+
acknowledged: literal(true)
|
|
14930
|
+
}),
|
|
14931
|
+
type: "rest"
|
|
14932
|
+
}
|
|
14933
|
+
}
|
|
14934
|
+
},
|
|
14900
14935
|
"/conduit/api/v1/source/:sourceID/requests/:requestID/chunk": {
|
|
14901
14936
|
POST: {
|
|
14902
14937
|
auth: {
|
|
@@ -15176,7 +15211,35 @@ const CompletionCreateParamsSchema = object({
|
|
|
15176
15211
|
user: string$1().optional()
|
|
15177
15212
|
});
|
|
15178
15213
|
|
|
15179
|
-
const
|
|
15214
|
+
const API_CLIENT_CONDUIT_GENERAL_REFERENCE = {
|
|
15215
|
+
"/conduit/engine/start": {
|
|
15216
|
+
POST: {
|
|
15217
|
+
auth: {
|
|
15218
|
+
type: "none"
|
|
15219
|
+
},
|
|
15220
|
+
response: {
|
|
15221
|
+
schema: object({
|
|
15222
|
+
acknowledged: literal(true)
|
|
15223
|
+
}),
|
|
15224
|
+
type: "rest"
|
|
15225
|
+
}
|
|
15226
|
+
}
|
|
15227
|
+
},
|
|
15228
|
+
"/conduit/engine/stop": {
|
|
15229
|
+
POST: {
|
|
15230
|
+
auth: {
|
|
15231
|
+
type: "none"
|
|
15232
|
+
},
|
|
15233
|
+
response: {
|
|
15234
|
+
schema: object({
|
|
15235
|
+
acknowledged: literal(true)
|
|
15236
|
+
}),
|
|
15237
|
+
type: "rest"
|
|
15238
|
+
}
|
|
15239
|
+
}
|
|
15240
|
+
}
|
|
15241
|
+
};
|
|
15242
|
+
const API_CLIENT_CONDUIT_OPENAI_REFERENCE = {
|
|
15180
15243
|
"/v1/chat/completions": {
|
|
15181
15244
|
POST: {
|
|
15182
15245
|
auth: {
|
|
@@ -98567,26 +98630,6 @@ async function startVLLM({ targetDirectory }) {
|
|
|
98567
98630
|
"1"
|
|
98568
98631
|
]
|
|
98569
98632
|
});
|
|
98570
|
-
processManager.on("error", err => {
|
|
98571
|
-
this.emit("engineError", new ProcessExecutionError({
|
|
98572
|
-
code: null,
|
|
98573
|
-
error: err,
|
|
98574
|
-
message: `Process error: ${VLLM_EXECUTABLE}: ${processManager.stderr}`,
|
|
98575
|
-
signal: null
|
|
98576
|
-
}));
|
|
98577
|
-
});
|
|
98578
|
-
processManager.on("stopped", (code, signal) => {
|
|
98579
|
-
if (code === 0) {
|
|
98580
|
-
this.emit("engineTerminated");
|
|
98581
|
-
}
|
|
98582
|
-
else {
|
|
98583
|
-
this.emit("engineError", new ProcessExecutionError({
|
|
98584
|
-
code,
|
|
98585
|
-
message: `Process stopped: ${VLLM_EXECUTABLE}: ${processManager.stderr}`,
|
|
98586
|
-
signal
|
|
98587
|
-
}));
|
|
98588
|
-
}
|
|
98589
|
-
});
|
|
98590
98633
|
await processManager.start();
|
|
98591
98634
|
return processManager;
|
|
98592
98635
|
}
|
|
@@ -108137,26 +108180,6 @@ async function startLlamacpp({ targetDirectory }) {
|
|
|
108137
108180
|
command: LLAMACPP_EXECUTABLE,
|
|
108138
108181
|
args
|
|
108139
108182
|
});
|
|
108140
|
-
processManager.on("error", err => {
|
|
108141
|
-
this.emit("engineError", new ProcessExecutionError({
|
|
108142
|
-
code: null,
|
|
108143
|
-
error: err,
|
|
108144
|
-
message: `Process error: ${LLAMACPP_EXECUTABLE}: ${processManager.stderr}`,
|
|
108145
|
-
signal: null
|
|
108146
|
-
}));
|
|
108147
|
-
});
|
|
108148
|
-
processManager.on("stopped", (code, signal) => {
|
|
108149
|
-
if (code === 0) {
|
|
108150
|
-
this.emit("engineTerminated");
|
|
108151
|
-
}
|
|
108152
|
-
else {
|
|
108153
|
-
this.emit("engineError", new ProcessExecutionError({
|
|
108154
|
-
code,
|
|
108155
|
-
message: `Process stopped: ${LLAMACPP_EXECUTABLE}: ${processManager.stderr}`,
|
|
108156
|
-
signal
|
|
108157
|
-
}));
|
|
108158
|
-
}
|
|
108159
|
-
});
|
|
108160
108183
|
await processManager.start();
|
|
108161
108184
|
return processManager;
|
|
108162
108185
|
}
|
|
@@ -108169,6 +108192,8 @@ class ModelManager extends EventEmitter {
|
|
|
108169
108192
|
contextLength;
|
|
108170
108193
|
logger;
|
|
108171
108194
|
engineProcess = null;
|
|
108195
|
+
lifecycleState = "stopped";
|
|
108196
|
+
stopRequested = false;
|
|
108172
108197
|
modelsDirectory;
|
|
108173
108198
|
constructor({ contextLength, engine, logger, model, parallelism, root }) {
|
|
108174
108199
|
super();
|
|
@@ -108257,57 +108282,66 @@ class ModelManager extends EventEmitter {
|
|
|
108257
108282
|
}
|
|
108258
108283
|
}
|
|
108259
108284
|
async start() {
|
|
108260
|
-
if (this.
|
|
108285
|
+
if (this.lifecycleState !== "stopped") {
|
|
108261
108286
|
throw new UnknownError({
|
|
108262
|
-
message:
|
|
108287
|
+
message: `Cannot start LLM engine: Invalid state: ${this.lifecycleState}`
|
|
108263
108288
|
});
|
|
108264
108289
|
}
|
|
108290
|
+
this.lifecycleState = "starting";
|
|
108291
|
+
this.stopRequested = false;
|
|
108265
108292
|
this.logger.info("Starting LLM engine", {
|
|
108266
108293
|
agentEngineType: this.engine
|
|
108267
108294
|
});
|
|
108268
|
-
switch (this.engine) {
|
|
108269
|
-
case "llama.cpp":
|
|
108270
|
-
this.engineProcess = await startLlamacpp.call(this, {
|
|
108271
|
-
targetDirectory: join(this.modelsDirectory, this.uniqueName)
|
|
108272
|
-
});
|
|
108273
|
-
break;
|
|
108274
|
-
case "vllm":
|
|
108275
|
-
this.engineProcess = await startVLLM.call(this, {
|
|
108276
|
-
targetDirectory: join(this.modelsDirectory, this.uniqueName)
|
|
108277
|
-
});
|
|
108278
|
-
break;
|
|
108279
|
-
// case "ollama":
|
|
108280
|
-
// this.engineProcess = await startOllama.call(this);
|
|
108281
|
-
// this.logger.info("Started LLM engine", {
|
|
108282
|
-
// agentEngineType: this.engine
|
|
108283
|
-
// });
|
|
108284
|
-
// return;
|
|
108285
|
-
default: {
|
|
108286
|
-
const engineType = this.engine;
|
|
108287
|
-
throw new ConfigurationInvalidError({
|
|
108288
|
-
message: `Cannot load local model: Invalid or unsupported engine: ${engineType}`
|
|
108289
|
-
});
|
|
108290
|
-
}
|
|
108291
|
-
}
|
|
108292
|
-
this.engineProcess.on("stderr", line => {
|
|
108293
|
-
console.error(`[${this.engine}]: ${line}`);
|
|
108294
|
-
});
|
|
108295
|
-
this.engineProcess.on("stdout", line => {
|
|
108296
|
-
console.log(`[${this.engine}]: ${line}`);
|
|
108297
|
-
});
|
|
108298
|
-
this.logger.info("Started LLM engine", {
|
|
108299
|
-
agentEngineType: this.engine
|
|
108300
|
-
});
|
|
108301
108295
|
try {
|
|
108296
|
+
this.engineProcess = await this.startEngineProcess();
|
|
108297
|
+
this.bindEngineProcessEvents(this.engineProcess);
|
|
108298
|
+
this.logger.info("Started LLM engine", {
|
|
108299
|
+
agentEngineType: this.engine
|
|
108300
|
+
});
|
|
108302
108301
|
await this.waitForEngineReady();
|
|
108303
108302
|
}
|
|
108304
108303
|
catch (error) {
|
|
108305
108304
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
108305
|
+
if (this.stopRequested) {
|
|
108306
|
+
throw err;
|
|
108307
|
+
}
|
|
108308
|
+
this.lifecycleState = "errored";
|
|
108306
108309
|
this.emit("engineError", err);
|
|
108307
108310
|
throw err;
|
|
108308
108311
|
}
|
|
108312
|
+
this.lifecycleState = "running";
|
|
108309
108313
|
this.emit("engineReady");
|
|
108310
108314
|
}
|
|
108315
|
+
async stop() {
|
|
108316
|
+
if (this.lifecycleState === "stopping") {
|
|
108317
|
+
throw new UnknownError({
|
|
108318
|
+
message: "Cannot stop LLM engine: already stopping"
|
|
108319
|
+
});
|
|
108320
|
+
}
|
|
108321
|
+
if (this.lifecycleState !== "running" && this.lifecycleState !== "starting") {
|
|
108322
|
+
throw new UnknownError({
|
|
108323
|
+
message: `Cannot stop LLM engine: Invalid state: ${this.lifecycleState}`
|
|
108324
|
+
});
|
|
108325
|
+
}
|
|
108326
|
+
const processManager = this.engineProcess;
|
|
108327
|
+
if (!processManager) {
|
|
108328
|
+
throw new UnknownError({
|
|
108329
|
+
message: "Cannot stop LLM engine: engine process not running"
|
|
108330
|
+
});
|
|
108331
|
+
}
|
|
108332
|
+
this.lifecycleState = "stopping";
|
|
108333
|
+
this.stopRequested = true;
|
|
108334
|
+
await processManager.stop();
|
|
108335
|
+
}
|
|
108336
|
+
get canStart() {
|
|
108337
|
+
return this.lifecycleState === "stopped";
|
|
108338
|
+
}
|
|
108339
|
+
get canStop() {
|
|
108340
|
+
return this.lifecycleState === "running" || this.lifecycleState === "starting";
|
|
108341
|
+
}
|
|
108342
|
+
get state() {
|
|
108343
|
+
return this.lifecycleState;
|
|
108344
|
+
}
|
|
108311
108345
|
async isEngineReady() {
|
|
108312
108346
|
switch (this.engine) {
|
|
108313
108347
|
case "llama.cpp":
|
|
@@ -108332,6 +108366,12 @@ class ModelManager extends EventEmitter {
|
|
|
108332
108366
|
const pollIntervalMs = 2000;
|
|
108333
108367
|
const start = Date.now();
|
|
108334
108368
|
while (Date.now() - start < maxWaitMs) {
|
|
108369
|
+
if (this.lifecycleState === "stopping") {
|
|
108370
|
+
throw new Error("LLM engine startup interrupted by stop request");
|
|
108371
|
+
}
|
|
108372
|
+
if (!this.engineProcess) {
|
|
108373
|
+
throw new Error("LLM engine process exited before readiness checks completed");
|
|
108374
|
+
}
|
|
108335
108375
|
const ready = await this.isEngineReady();
|
|
108336
108376
|
if (ready) {
|
|
108337
108377
|
return;
|
|
@@ -108340,6 +108380,78 @@ class ModelManager extends EventEmitter {
|
|
|
108340
108380
|
}
|
|
108341
108381
|
throw new Error("LLM engine failed readiness checks within timeout");
|
|
108342
108382
|
}
|
|
108383
|
+
bindEngineProcessEvents(processManager) {
|
|
108384
|
+
let hasTerminated = false;
|
|
108385
|
+
processManager.on("stderr", line => {
|
|
108386
|
+
console.error(`[${this.engine}]: ${line}`);
|
|
108387
|
+
});
|
|
108388
|
+
processManager.on("stdout", line => {
|
|
108389
|
+
console.log(`[${this.engine}]: ${line}`);
|
|
108390
|
+
});
|
|
108391
|
+
processManager.on("error", err => {
|
|
108392
|
+
if (hasTerminated) {
|
|
108393
|
+
return;
|
|
108394
|
+
}
|
|
108395
|
+
hasTerminated = true;
|
|
108396
|
+
if (this.stopRequested) {
|
|
108397
|
+
this.engineProcess = null;
|
|
108398
|
+
this.lifecycleState = "stopped";
|
|
108399
|
+
this.stopRequested = false;
|
|
108400
|
+
this.emit("engineTerminated");
|
|
108401
|
+
return;
|
|
108402
|
+
}
|
|
108403
|
+
this.engineProcess = null;
|
|
108404
|
+
this.lifecycleState = "errored";
|
|
108405
|
+
this.emit("engineError", new ProcessExecutionError({
|
|
108406
|
+
code: null,
|
|
108407
|
+
error: err,
|
|
108408
|
+
message: `Process error: ${this.engine}: ${processManager.stderr}`,
|
|
108409
|
+
signal: null
|
|
108410
|
+
}));
|
|
108411
|
+
});
|
|
108412
|
+
processManager.on("stopped", (code, signal) => {
|
|
108413
|
+
if (hasTerminated) {
|
|
108414
|
+
return;
|
|
108415
|
+
}
|
|
108416
|
+
hasTerminated = true;
|
|
108417
|
+
this.engineProcess = null;
|
|
108418
|
+
if (this.stopRequested) {
|
|
108419
|
+
this.lifecycleState = "stopped";
|
|
108420
|
+
this.stopRequested = false;
|
|
108421
|
+
this.emit("engineTerminated");
|
|
108422
|
+
return;
|
|
108423
|
+
}
|
|
108424
|
+
if (code === 0) {
|
|
108425
|
+
this.lifecycleState = "stopped";
|
|
108426
|
+
this.emit("engineTerminated");
|
|
108427
|
+
return;
|
|
108428
|
+
}
|
|
108429
|
+
this.lifecycleState = "errored";
|
|
108430
|
+
this.emit("engineError", new ProcessExecutionError({
|
|
108431
|
+
code,
|
|
108432
|
+
message: `Process stopped: ${this.engine}: ${processManager.stderr}`,
|
|
108433
|
+
signal
|
|
108434
|
+
}));
|
|
108435
|
+
});
|
|
108436
|
+
}
|
|
108437
|
+
async startEngineProcess() {
|
|
108438
|
+
switch (this.engine) {
|
|
108439
|
+
case "llama.cpp":
|
|
108440
|
+
return startLlamacpp.call(this, {
|
|
108441
|
+
targetDirectory: join(this.modelsDirectory, this.uniqueName)
|
|
108442
|
+
});
|
|
108443
|
+
case "vllm":
|
|
108444
|
+
return startVLLM.call(this, {
|
|
108445
|
+
targetDirectory: join(this.modelsDirectory, this.uniqueName)
|
|
108446
|
+
});
|
|
108447
|
+
default: {
|
|
108448
|
+
const engineType = this.engine;
|
|
108449
|
+
throw new ConfigurationInvalidError({
|
|
108450
|
+
message: `Cannot load local model: Invalid or unsupported engine: ${engineType}`
|
|
108451
|
+
});
|
|
108452
|
+
}
|
|
108453
|
+
}
|
|
108454
|
+
}
|
|
108343
108455
|
}
|
|
108344
108456
|
|
|
108345
108457
|
function sleep(ms) {
|
|
@@ -108774,6 +108886,13 @@ class ConduitStateReportManager {
|
|
|
108774
108886
|
reportDownloadProgress() {
|
|
108775
108887
|
this.scheduleConduitStateReport();
|
|
108776
108888
|
}
|
|
108889
|
+
async reportNow() {
|
|
108890
|
+
if (this.pendingConduitStateReport) {
|
|
108891
|
+
clearTimeout(this.pendingConduitStateReport);
|
|
108892
|
+
this.pendingConduitStateReport = null;
|
|
108893
|
+
}
|
|
108894
|
+
await this.triggerConduitStateReport();
|
|
108895
|
+
}
|
|
108777
108896
|
reportStateChange() {
|
|
108778
108897
|
if (this.pendingConduitStateReport) {
|
|
108779
108898
|
clearTimeout(this.pendingConduitStateReport);
|
|
@@ -118681,15 +118800,6 @@ async function createApplication({ abortController, apiClient, configuration, lo
|
|
|
118681
118800
|
parallelism: conduitConfiguration.parallelism ?? null,
|
|
118682
118801
|
root: configuration.rootDirectory
|
|
118683
118802
|
});
|
|
118684
|
-
conduitStateManager.setState({
|
|
118685
|
-
modelFileName,
|
|
118686
|
-
modelName,
|
|
118687
|
-
state: "downloadingModelFiles",
|
|
118688
|
-
totalProgress: {
|
|
118689
|
-
file: 0,
|
|
118690
|
-
total: 0
|
|
118691
|
-
}
|
|
118692
|
-
});
|
|
118693
118803
|
const conduitStateReportManager = new ConduitStateReportManager({
|
|
118694
118804
|
apiClient,
|
|
118695
118805
|
conduitStateManager,
|
|
@@ -118698,6 +118808,30 @@ async function createApplication({ abortController, apiClient, configuration, lo
|
|
|
118698
118808
|
stateIntervalMs: 30000
|
|
118699
118809
|
});
|
|
118700
118810
|
await conduitStateReportManager.start();
|
|
118811
|
+
const setIdleState = ({ reason }) => {
|
|
118812
|
+
conduitStateManager.setState({
|
|
118813
|
+
reason,
|
|
118814
|
+
state: "idle"
|
|
118815
|
+
});
|
|
118816
|
+
conduitStateReportManager.reportStateChange();
|
|
118817
|
+
};
|
|
118818
|
+
const setOnlineState = () => {
|
|
118819
|
+
if (conduitStateManager.getState().state === "online") {
|
|
118820
|
+
return;
|
|
118821
|
+
}
|
|
118822
|
+
conduitStateManager.setState({
|
|
118823
|
+
modelName,
|
|
118824
|
+
state: "online"
|
|
118825
|
+
});
|
|
118826
|
+
conduitStateReportManager.reportStateChange();
|
|
118827
|
+
};
|
|
118828
|
+
const setErrorState = ({ error }) => {
|
|
118829
|
+
conduitStateManager.setState({
|
|
118830
|
+
error,
|
|
118831
|
+
state: "error"
|
|
118832
|
+
});
|
|
118833
|
+
conduitStateReportManager.reportStateChange();
|
|
118834
|
+
};
|
|
118701
118835
|
let lastDownloadKey = "";
|
|
118702
118836
|
const reportDownloadProgress = (update) => {
|
|
118703
118837
|
const filePercent = update.file.total > 0 ? Math.floor((update.file.bytes / update.file.total) * 100) : 0;
|
|
@@ -118721,14 +118855,75 @@ async function createApplication({ abortController, apiClient, configuration, lo
|
|
|
118721
118855
|
});
|
|
118722
118856
|
conduitStateReportManager.reportDownloadProgress();
|
|
118723
118857
|
};
|
|
118724
|
-
|
|
118725
|
-
|
|
118858
|
+
let stopRequestedByControl = false;
|
|
118859
|
+
async function startEngine() {
|
|
118860
|
+
logger.info("Engine start requested");
|
|
118861
|
+
conduitStateManager.setState({
|
|
118862
|
+
modelFileName,
|
|
118863
|
+
modelName,
|
|
118864
|
+
state: "downloadingModelFiles",
|
|
118865
|
+
totalProgress: {
|
|
118866
|
+
file: 0,
|
|
118867
|
+
total: 0
|
|
118868
|
+
}
|
|
118869
|
+
});
|
|
118870
|
+
await conduitStateReportManager.reportNow();
|
|
118871
|
+
await modelManager.prepare({
|
|
118872
|
+
onDownloadProgress: reportDownloadProgress
|
|
118873
|
+
});
|
|
118874
|
+
conduitStateManager.setState({
|
|
118875
|
+
state: "bootingEngine"
|
|
118876
|
+
});
|
|
118877
|
+
await conduitStateReportManager.reportNow();
|
|
118878
|
+
await modelManager.start();
|
|
118879
|
+
}
|
|
118880
|
+
async function stopEngine({ reason }) {
|
|
118881
|
+
if (!modelManager.canStop) {
|
|
118882
|
+
throw new Error("Engine is not in a stoppable state");
|
|
118883
|
+
}
|
|
118884
|
+
stopRequestedByControl = true;
|
|
118885
|
+
conduitStateManager.setState({
|
|
118886
|
+
state: "stoppingEngine"
|
|
118887
|
+
});
|
|
118888
|
+
await conduitStateReportManager.reportNow();
|
|
118889
|
+
logger.info("Stopping engine process");
|
|
118890
|
+
await modelManager.stop();
|
|
118891
|
+
logger.info("Engine process stopped");
|
|
118892
|
+
setIdleState({ reason });
|
|
118893
|
+
}
|
|
118894
|
+
modelManager.on("engineError", err => {
|
|
118895
|
+
logger.error("LLM engine error", {
|
|
118896
|
+
error: err
|
|
118897
|
+
});
|
|
118898
|
+
stopRequestedByControl = false;
|
|
118899
|
+
setErrorState({ error: err.message });
|
|
118726
118900
|
});
|
|
118727
|
-
|
|
118728
|
-
|
|
118901
|
+
modelManager.on("engineTerminated", () => {
|
|
118902
|
+
if (stopRequestedByControl) {
|
|
118903
|
+
stopRequestedByControl = false;
|
|
118904
|
+
setIdleState({ reason: "Remote shutdown requested" });
|
|
118905
|
+
return;
|
|
118906
|
+
}
|
|
118907
|
+
conduitStateManager.setState({
|
|
118908
|
+
state: "offline"
|
|
118909
|
+
});
|
|
118910
|
+
conduitStateReportManager.reportStateChange();
|
|
118729
118911
|
});
|
|
118730
|
-
|
|
118731
|
-
|
|
118912
|
+
modelManager.on("engineReady", () => {
|
|
118913
|
+
setOnlineState();
|
|
118914
|
+
});
|
|
118915
|
+
if (configuration.startMode === "idle") {
|
|
118916
|
+
setIdleState({ reason: "Startup mode is idle" });
|
|
118917
|
+
}
|
|
118918
|
+
else {
|
|
118919
|
+
await startEngine().catch(error => {
|
|
118920
|
+
const parsedError = asError(error);
|
|
118921
|
+
logger.error("Failed starting LLM engine", {
|
|
118922
|
+
error: parsedError
|
|
118923
|
+
});
|
|
118924
|
+
setErrorState({ error: parsedError.message });
|
|
118925
|
+
});
|
|
118926
|
+
}
|
|
118732
118927
|
// #region API routes
|
|
118733
118928
|
const app = express();
|
|
118734
118929
|
const publicRouter = createRouter();
|
|
@@ -118736,6 +118931,96 @@ async function createApplication({ abortController, apiClient, configuration, lo
|
|
|
118736
118931
|
publicRouter.get("/health", (_req, res) => {
|
|
118737
118932
|
res.status(200).send("OK");
|
|
118738
118933
|
});
|
|
118934
|
+
implementAPIReference({
|
|
118935
|
+
api: {
|
|
118936
|
+
"/conduit/engine/start": {
|
|
118937
|
+
POST: async () => {
|
|
118938
|
+
if (conduitStateManager.getState().state !== "idle") {
|
|
118939
|
+
return {
|
|
118940
|
+
status: 409,
|
|
118941
|
+
statusText: "Engine can only be started from idle state"
|
|
118942
|
+
};
|
|
118943
|
+
}
|
|
118944
|
+
if (!modelManager.canStart) {
|
|
118945
|
+
return {
|
|
118946
|
+
status: 409,
|
|
118947
|
+
statusText: `Engine cannot be started from current state: ${modelManager.state}`
|
|
118948
|
+
};
|
|
118949
|
+
}
|
|
118950
|
+
try {
|
|
118951
|
+
logger.info("Received remote engine start request");
|
|
118952
|
+
await startEngine();
|
|
118953
|
+
return {
|
|
118954
|
+
body: {
|
|
118955
|
+
acknowledged: true
|
|
118956
|
+
},
|
|
118957
|
+
status: 202
|
|
118958
|
+
};
|
|
118959
|
+
}
|
|
118960
|
+
catch (error) {
|
|
118961
|
+
if (stopRequestedByControl || modelManager.state === "stopped") {
|
|
118962
|
+
return {
|
|
118963
|
+
status: 409,
|
|
118964
|
+
statusText: "Engine start was interrupted"
|
|
118965
|
+
};
|
|
118966
|
+
}
|
|
118967
|
+
const parsedError = asError(error);
|
|
118968
|
+
setErrorState({ error: parsedError.message });
|
|
118969
|
+
return {
|
|
118970
|
+
status: 500,
|
|
118971
|
+
statusText: parsedError.message
|
|
118972
|
+
};
|
|
118973
|
+
}
|
|
118974
|
+
}
|
|
118975
|
+
},
|
|
118976
|
+
"/conduit/engine/stop": {
|
|
118977
|
+
POST: async () => {
|
|
118978
|
+
const sourceState = conduitStateManager.getState().state;
|
|
118979
|
+
if (sourceState !== "bootingEngine" && sourceState !== "online") {
|
|
118980
|
+
return {
|
|
118981
|
+
status: 409,
|
|
118982
|
+
statusText: "Engine can only be stopped while booting or online"
|
|
118983
|
+
};
|
|
118984
|
+
}
|
|
118985
|
+
if (!modelManager.canStop) {
|
|
118986
|
+
return {
|
|
118987
|
+
status: 409,
|
|
118988
|
+
statusText: `Engine cannot be stopped from current state: ${modelManager.state}`
|
|
118989
|
+
};
|
|
118990
|
+
}
|
|
118991
|
+
try {
|
|
118992
|
+
logger.info("Received remote engine stop request");
|
|
118993
|
+
stopEngine({
|
|
118994
|
+
reason: "Remote shutdown requested"
|
|
118995
|
+
}).catch(error => {
|
|
118996
|
+
const parsedError = asError(error);
|
|
118997
|
+
logger.error("Remote engine stop request failed", {
|
|
118998
|
+
error: parsedError
|
|
118999
|
+
});
|
|
119000
|
+
setErrorState({ error: parsedError.message });
|
|
119001
|
+
});
|
|
119002
|
+
return {
|
|
119003
|
+
body: {
|
|
119004
|
+
acknowledged: true
|
|
119005
|
+
},
|
|
119006
|
+
status: 202
|
|
119007
|
+
};
|
|
119008
|
+
}
|
|
119009
|
+
catch (error) {
|
|
119010
|
+
const parsedError = asError(error);
|
|
119011
|
+
setErrorState({ error: parsedError.message });
|
|
119012
|
+
return {
|
|
119013
|
+
status: 500,
|
|
119014
|
+
statusText: parsedError.message
|
|
119015
|
+
};
|
|
119016
|
+
}
|
|
119017
|
+
}
|
|
119018
|
+
}
|
|
119019
|
+
},
|
|
119020
|
+
logger,
|
|
119021
|
+
mount: publicRouter,
|
|
119022
|
+
reference: API_CLIENT_CONDUIT_GENERAL_REFERENCE
|
|
119023
|
+
});
|
|
118739
119024
|
implementAPIReference({
|
|
118740
119025
|
api: {
|
|
118741
119026
|
"/v1/chat/completions": {
|
|
@@ -118793,39 +119078,7 @@ async function createApplication({ abortController, apiClient, configuration, lo
|
|
|
118793
119078
|
},
|
|
118794
119079
|
logger,
|
|
118795
119080
|
mount: publicRouter,
|
|
118796
|
-
reference:
|
|
118797
|
-
});
|
|
118798
|
-
let activeRequests = 0;
|
|
118799
|
-
const setOnlineState = () => {
|
|
118800
|
-
if (conduitStateManager.getState().state === "online") {
|
|
118801
|
-
return;
|
|
118802
|
-
}
|
|
118803
|
-
conduitStateManager.setState({
|
|
118804
|
-
modelName,
|
|
118805
|
-
state: "online"
|
|
118806
|
-
});
|
|
118807
|
-
conduitStateReportManager.reportStateChange();
|
|
118808
|
-
};
|
|
118809
|
-
modelManager.on("engineError", err => {
|
|
118810
|
-
logger.error("LLM engine error", {
|
|
118811
|
-
error: err
|
|
118812
|
-
});
|
|
118813
|
-
conduitStateManager.setState({
|
|
118814
|
-
error: err.message,
|
|
118815
|
-
state: "error"
|
|
118816
|
-
});
|
|
118817
|
-
conduitStateReportManager.reportStateChange();
|
|
118818
|
-
abortController.abort(err);
|
|
118819
|
-
});
|
|
118820
|
-
modelManager.on("engineTerminated", () => {
|
|
118821
|
-
conduitStateManager.setState({
|
|
118822
|
-
state: "offline"
|
|
118823
|
-
});
|
|
118824
|
-
conduitStateReportManager.reportStateChange();
|
|
118825
|
-
abortController.abort();
|
|
118826
|
-
});
|
|
118827
|
-
modelManager.on("engineReady", () => {
|
|
118828
|
-
setOnlineState();
|
|
119081
|
+
reference: API_CLIENT_CONDUIT_OPENAI_REFERENCE
|
|
118829
119082
|
});
|
|
118830
119083
|
handleSSERequests({
|
|
118831
119084
|
apiURL: configuration.apiURL,
|
|
@@ -118839,16 +119092,10 @@ async function createApplication({ abortController, apiClient, configuration, lo
|
|
|
118839
119092
|
});
|
|
118840
119093
|
},
|
|
118841
119094
|
onRequestEnd: () => {
|
|
118842
|
-
|
|
118843
|
-
if (activeRequests === 0) {
|
|
118844
|
-
setOnlineState();
|
|
118845
|
-
}
|
|
119095
|
+
return;
|
|
118846
119096
|
},
|
|
118847
119097
|
onRequestStart: () => {
|
|
118848
|
-
|
|
118849
|
-
if (activeRequests === 1) {
|
|
118850
|
-
setOnlineState();
|
|
118851
|
-
}
|
|
119098
|
+
return;
|
|
118852
119099
|
},
|
|
118853
119100
|
reportMetrics: apiClient.reportPromptMetrics,
|
|
118854
119101
|
signal: abortController.signal
|
|
@@ -118857,7 +119104,55 @@ async function createApplication({ abortController, apiClient, configuration, lo
|
|
|
118857
119104
|
error: asError(error)
|
|
118858
119105
|
});
|
|
118859
119106
|
});
|
|
118860
|
-
|
|
119107
|
+
let shutdownPromise = null;
|
|
119108
|
+
async function shutdown() {
|
|
119109
|
+
if (shutdownPromise) {
|
|
119110
|
+
await shutdownPromise;
|
|
119111
|
+
return;
|
|
119112
|
+
}
|
|
119113
|
+
shutdownPromise = (async () => {
|
|
119114
|
+
logger.info("Conduit shutdown requested");
|
|
119115
|
+
if (modelManager.canStop) {
|
|
119116
|
+
conduitStateManager.setState({
|
|
119117
|
+
state: "stoppingEngine"
|
|
119118
|
+
});
|
|
119119
|
+
try {
|
|
119120
|
+
await conduitStateReportManager.reportNow();
|
|
119121
|
+
}
|
|
119122
|
+
catch (error) {
|
|
119123
|
+
logger.warn("Failed to report stopping conduit state", {
|
|
119124
|
+
error: asError(error)
|
|
119125
|
+
});
|
|
119126
|
+
}
|
|
119127
|
+
try {
|
|
119128
|
+
await modelManager.stop();
|
|
119129
|
+
}
|
|
119130
|
+
catch (error) {
|
|
119131
|
+
logger.warn("Failed to stop model process during shutdown", {
|
|
119132
|
+
error: asError(error)
|
|
119133
|
+
});
|
|
119134
|
+
}
|
|
119135
|
+
}
|
|
119136
|
+
conduitStateManager.setState({
|
|
119137
|
+
state: "offline"
|
|
119138
|
+
});
|
|
119139
|
+
try {
|
|
119140
|
+
await conduitStateReportManager.reportNow();
|
|
119141
|
+
}
|
|
119142
|
+
catch (error) {
|
|
119143
|
+
logger.warn("Failed to report offline conduit state", {
|
|
119144
|
+
error: asError(error)
|
|
119145
|
+
});
|
|
119146
|
+
}
|
|
119147
|
+
conduitStateReportManager.stop();
|
|
119148
|
+
abortController.abort();
|
|
119149
|
+
})();
|
|
119150
|
+
await shutdownPromise;
|
|
119151
|
+
}
|
|
119152
|
+
return {
|
|
119153
|
+
app,
|
|
119154
|
+
shutdown
|
|
119155
|
+
};
|
|
118861
119156
|
// #endregion
|
|
118862
119157
|
}
|
|
118863
119158
|
function getConduitModelFileName(configuration) {
|
|
@@ -118868,6 +119163,7 @@ function getConduitModelName(configuration) {
|
|
|
118868
119163
|
return configuration.targetModel.id;
|
|
118869
119164
|
}
|
|
118870
119165
|
|
|
119166
|
+
const StartModeSchema = _enum(["auto", "idle"]);
|
|
118871
119167
|
function getConfiguration({ overrides } = {}) {
|
|
118872
119168
|
const agentEngineTypeValue = overrides?.agentEngineType ?? readEnvString("ENGINE");
|
|
118873
119169
|
const agentEngineType = LLMEngineSchema.parse(agentEngineTypeValue);
|
|
@@ -118881,13 +119177,16 @@ function getConfiguration({ overrides } = {}) {
|
|
|
118881
119177
|
}
|
|
118882
119178
|
const defaultRootDirectory = join(process.env.HOME ?? "/tmp", ".cache", "infersec", "iagent");
|
|
118883
119179
|
const rootDirectory = overrides?.rootDirectory ?? readEnvStringOptional("ROOT_DIRECTORY", defaultRootDirectory);
|
|
119180
|
+
const startModeValue = overrides?.startMode ?? readEnvStringOptional("START_MODE", "auto");
|
|
119181
|
+
const startMode = StartModeSchema.parse(startModeValue);
|
|
118884
119182
|
return {
|
|
118885
119183
|
agentEngineType,
|
|
118886
119184
|
apiKey,
|
|
118887
119185
|
apiURL,
|
|
118888
119186
|
inferenceSourceID,
|
|
118889
119187
|
port,
|
|
118890
|
-
rootDirectory
|
|
119188
|
+
rootDirectory,
|
|
119189
|
+
startMode
|
|
118891
119190
|
};
|
|
118892
119191
|
}
|
|
118893
119192
|
|
|
@@ -118906,15 +119205,20 @@ async function startInferenceAgent({ configurationOverrides }) {
|
|
|
118906
119205
|
inferenceSourceID: configuration.inferenceSourceID
|
|
118907
119206
|
});
|
|
118908
119207
|
logger.info("Starting web server");
|
|
118909
|
-
const app = await createApplication({
|
|
119208
|
+
const { app, shutdown } = await createApplication({
|
|
119209
|
+
abortController,
|
|
119210
|
+
apiClient,
|
|
119211
|
+
configuration,
|
|
119212
|
+
logger
|
|
119213
|
+
});
|
|
118910
119214
|
await new Promise(resolve => {
|
|
118911
119215
|
app.listen(configuration.port, () => {
|
|
118912
119216
|
logger.info("Server listening", { port: configuration.port });
|
|
118913
119217
|
resolve();
|
|
118914
119218
|
});
|
|
118915
119219
|
});
|
|
118916
|
-
process.on("SIGINT", createSignalShutdown({ logger }));
|
|
118917
|
-
process.on("SIGTERM", createSignalShutdown({ logger }));
|
|
119220
|
+
process.on("SIGINT", createSignalShutdown({ logger, shutdown }));
|
|
119221
|
+
process.on("SIGTERM", createSignalShutdown({ logger, shutdown }));
|
|
118918
119222
|
abortController.signal.addEventListener("abort", () => {
|
|
118919
119223
|
if (abortController.signal.reason instanceof Error) {
|
|
118920
119224
|
process.exit(1);
|
|
@@ -118922,13 +119226,18 @@ async function startInferenceAgent({ configurationOverrides }) {
|
|
|
118922
119226
|
process.exit(0);
|
|
118923
119227
|
});
|
|
118924
119228
|
}
|
|
118925
|
-
function createSignalShutdown({ logger }) {
|
|
119229
|
+
function createSignalShutdown({ logger, shutdown }) {
|
|
119230
|
+
let shutdownPromise = null;
|
|
118926
119231
|
return (signal) => {
|
|
118927
119232
|
Promise.resolve()
|
|
118928
119233
|
.then(async () => {
|
|
118929
119234
|
logger.info("Received shutdown signal", {
|
|
118930
119235
|
signal
|
|
118931
119236
|
});
|
|
119237
|
+
if (!shutdownPromise) {
|
|
119238
|
+
shutdownPromise = shutdown();
|
|
119239
|
+
}
|
|
119240
|
+
await shutdownPromise;
|
|
118932
119241
|
await sleep(500);
|
|
118933
119242
|
process.exit(0);
|
|
118934
119243
|
})
|