@infersec/conduit 1.6.2 → 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-
|
|
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-
|
|
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(
|
|
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
|
|
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>;
|
package/dist/sse/handler.d.ts
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
114747
|
-
state: "
|
|
114810
|
+
reason: idleReason,
|
|
114811
|
+
state: "idle"
|
|
114748
114812
|
});
|
|
114749
114813
|
});
|
|
114750
114814
|
conduitStateManager.setState({
|
|
114751
|
-
modelFileName
|
|
114752
|
-
modelName
|
|
114815
|
+
modelFileName,
|
|
114816
|
+
modelName,
|
|
114753
114817
|
state: "downloadingModelFiles",
|
|
114754
114818
|
totalProgress: {
|
|
114755
114819
|
file: 0,
|
|
114756
114820
|
total: 0
|
|
114757
114821
|
}
|
|
114758
114822
|
});
|
|
114759
|
-
|
|
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", {
|