solver-sdk 1.6.8 → 1.7.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 +16 -21
- package/dist/cjs/utils/code-solver-websocket-client.js +980 -50
- package/dist/cjs/utils/code-solver-websocket-client.js.map +1 -1
- package/dist/esm/utils/code-solver-websocket-client.js +980 -50
- package/dist/esm/utils/code-solver-websocket-client.js.map +1 -1
- package/dist/types/utils/code-solver-websocket-client.d.ts +304 -6
- package/dist/types/utils/code-solver-websocket-client.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -34,6 +34,24 @@ export class CodeSolverWebSocketClient {
|
|
|
34
34
|
this.activeProjectId = null;
|
|
35
35
|
/** Обработчики событий мышления */
|
|
36
36
|
this.thinkingEventHandlers = new Map();
|
|
37
|
+
/** Таймеры для ping/pong */
|
|
38
|
+
this.pingIntervals = new Map();
|
|
39
|
+
/** Статистика ping/pong */
|
|
40
|
+
this.pingStats = new Map();
|
|
41
|
+
/** Количество последовательных таймаутов */
|
|
42
|
+
this.pingTimeouts = new Map();
|
|
43
|
+
/** Задержка по умолчанию между ping-сообщениями (30 секунд) */
|
|
44
|
+
this.defaultPingInterval = 30000;
|
|
45
|
+
/** Порог таймаута (количество пропущенных pong) */
|
|
46
|
+
this.timeoutThreshold = 3;
|
|
47
|
+
/** Хранилище обработчиков ping/pong */
|
|
48
|
+
this.pingPongEventHandlers = new Map();
|
|
49
|
+
/** Токены сессий для разных пространств имен */
|
|
50
|
+
this.sessionTokens = new Map();
|
|
51
|
+
/** Состояние подключения для разных пространств имен */
|
|
52
|
+
this.connectionState = new Map();
|
|
53
|
+
/** Таймер для проверки здоровья соединений */
|
|
54
|
+
this.healthCheckTimer = null;
|
|
37
55
|
this.baseURL = baseURL.replace(/^http/, 'ws');
|
|
38
56
|
this.options = {
|
|
39
57
|
...options,
|
|
@@ -176,54 +194,6 @@ export class CodeSolverWebSocketClient {
|
|
|
176
194
|
this.clients.set(namespace, client);
|
|
177
195
|
return client;
|
|
178
196
|
}
|
|
179
|
-
/**
|
|
180
|
-
* Подключается к пространству имен рассуждений
|
|
181
|
-
* @param reasoningId ID рассуждения (опционально)
|
|
182
|
-
* @returns Promise с результатом подключения
|
|
183
|
-
*/
|
|
184
|
-
async connectToReasoning(reasoningId) {
|
|
185
|
-
try {
|
|
186
|
-
this.logger('info', 'Подключение к пространству имен рассуждений', { reasoningId });
|
|
187
|
-
// Если указан ID рассуждения, сохраняем его
|
|
188
|
-
if (reasoningId) {
|
|
189
|
-
this.activeReasoningId = reasoningId;
|
|
190
|
-
}
|
|
191
|
-
// Подключаемся к пространству имен рассуждений
|
|
192
|
-
const client = await this.connect(WebSocketNamespace.REASONING);
|
|
193
|
-
// Аутентифицируемся с увеличенным таймаутом
|
|
194
|
-
try {
|
|
195
|
-
const authResult = await client.emitWithAck(WsEvents.AUTHENTICATE, {
|
|
196
|
-
token: this.options.apiKey,
|
|
197
|
-
reasoningId: this.activeReasoningId
|
|
198
|
-
}, 10000);
|
|
199
|
-
this.logger('debug', 'Результат аутентификации в namespace рассуждений', authResult);
|
|
200
|
-
}
|
|
201
|
-
catch (error) {
|
|
202
|
-
this.logger('error', 'Ошибка аутентификации в namespace рассуждений', error);
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
// Если у нас есть ID рассуждения, присоединяемся к нему
|
|
206
|
-
if (this.activeReasoningId) {
|
|
207
|
-
try {
|
|
208
|
-
const joinResult = await client.emitWithAck(WsEvents.JOIN_REASONING, {
|
|
209
|
-
reasoningId: this.activeReasoningId,
|
|
210
|
-
token: this.options.apiKey
|
|
211
|
-
}, 10000);
|
|
212
|
-
this.logger('debug', 'Результат присоединения к рассуждению', joinResult);
|
|
213
|
-
return true;
|
|
214
|
-
}
|
|
215
|
-
catch (error) {
|
|
216
|
-
this.logger('error', 'Ошибка присоединения к рассуждению', error);
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
this.logger('error', 'Ошибка подключения к пространству имен рассуждений', error);
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
197
|
/**
|
|
228
198
|
* Подключается к пространству имен индексации
|
|
229
199
|
* @param projectId ID проекта (опционально)
|
|
@@ -354,11 +324,17 @@ export class CodeSolverWebSocketClient {
|
|
|
354
324
|
}
|
|
355
325
|
/**
|
|
356
326
|
* Отключается от всех пространств имен
|
|
327
|
+
* Отключает автоматический механизм ping/pong
|
|
357
328
|
*/
|
|
358
329
|
disconnectAll() {
|
|
330
|
+
// Отключаем ping/pong для всех соединений
|
|
331
|
+
this.disablePingPong();
|
|
332
|
+
// Отключаемся от всех namespace
|
|
359
333
|
for (const [namespace, client] of this.clients.entries()) {
|
|
360
|
-
client
|
|
361
|
-
|
|
334
|
+
if (client) {
|
|
335
|
+
client.close();
|
|
336
|
+
this.clients.delete(namespace);
|
|
337
|
+
}
|
|
362
338
|
}
|
|
363
339
|
// Сбрасываем активные сессии
|
|
364
340
|
this.activeReasoningId = null;
|
|
@@ -695,5 +671,959 @@ export class CodeSolverWebSocketClient {
|
|
|
695
671
|
}
|
|
696
672
|
}
|
|
697
673
|
}
|
|
674
|
+
/**
|
|
675
|
+
* Включить автоматическую отправку ping-сообщений и сбор статистики
|
|
676
|
+
* @param {number} interval - Интервал между ping-сообщениями в миллисекундах
|
|
677
|
+
* @param {number} timeoutThreshold - Количество пропущенных pong-сообщений, после которого соединение считается потерянным
|
|
678
|
+
* @returns {boolean} - Успешность включения ping/pong
|
|
679
|
+
*/
|
|
680
|
+
enablePingPong(interval = this.defaultPingInterval, timeoutThreshold = 3) {
|
|
681
|
+
// Сохраняем порог таймаута
|
|
682
|
+
this.timeoutThreshold = timeoutThreshold;
|
|
683
|
+
// Для каждого активного соединения
|
|
684
|
+
for (const [namespace, client] of this.clients.entries()) {
|
|
685
|
+
try {
|
|
686
|
+
// Проверяем, активно ли соединение
|
|
687
|
+
if (!client || !this.isConnected(namespace)) {
|
|
688
|
+
this.logger('warn', `Невозможно включить ping/pong для неактивного соединения в ${namespace}`);
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
// Останавливаем существующий таймер, если есть
|
|
692
|
+
this.disablePingPong(namespace);
|
|
693
|
+
// Инициализируем статистику, если не была создана
|
|
694
|
+
if (!this.pingStats.has(namespace)) {
|
|
695
|
+
this.pingStats.set(namespace, {
|
|
696
|
+
namespace,
|
|
697
|
+
socketId: client.getSocketId(),
|
|
698
|
+
pingSent: 0,
|
|
699
|
+
pongReceived: 0,
|
|
700
|
+
averageRtt: 0,
|
|
701
|
+
minRtt: Number.MAX_SAFE_INTEGER,
|
|
702
|
+
maxRtt: 0,
|
|
703
|
+
lastRtt: 0,
|
|
704
|
+
lastPongTimestamp: Date.now(),
|
|
705
|
+
isConnected: true
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
// Сбрасываем счетчик таймаутов
|
|
709
|
+
this.pingTimeouts.set(namespace, 0);
|
|
710
|
+
// Устанавливаем обработчик для события connection_pong
|
|
711
|
+
client.on(WsEvents.CONNECTION_PONG, (data) => {
|
|
712
|
+
// Обновляем статистику
|
|
713
|
+
const stats = this.pingStats.get(namespace);
|
|
714
|
+
if (stats) {
|
|
715
|
+
stats.pongReceived++;
|
|
716
|
+
stats.lastPongTimestamp = Date.now();
|
|
717
|
+
stats.isConnected = true;
|
|
718
|
+
// Рассчитываем RTT, если есть метка времени эхо
|
|
719
|
+
if (data && data.echo) {
|
|
720
|
+
const rtt = Date.now() - data.echo;
|
|
721
|
+
stats.lastRtt = rtt;
|
|
722
|
+
// Обновляем min и max
|
|
723
|
+
stats.minRtt = Math.min(stats.minRtt, rtt);
|
|
724
|
+
stats.maxRtt = Math.max(stats.maxRtt, rtt);
|
|
725
|
+
// Обновляем среднее значение
|
|
726
|
+
stats.averageRtt = (stats.averageRtt * (stats.pongReceived - 1) + rtt) / stats.pongReceived;
|
|
727
|
+
}
|
|
728
|
+
// Сбрасываем счетчик таймаутов
|
|
729
|
+
this.pingTimeouts.set(namespace, 0);
|
|
730
|
+
}
|
|
731
|
+
// Логируем получение pong
|
|
732
|
+
this.logger('debug', `Получен pong для ${namespace}`, {
|
|
733
|
+
rtt: stats?.lastRtt,
|
|
734
|
+
socketId: client.getSocketId()
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
// Устанавливаем интервал отправки ping
|
|
738
|
+
const pingInterval = setInterval(() => {
|
|
739
|
+
if (this.isConnected(namespace)) {
|
|
740
|
+
// Формируем данные ping
|
|
741
|
+
const pingData = { timestamp: Date.now() };
|
|
742
|
+
// Отправляем ping
|
|
743
|
+
const sent = this.send(namespace, WsEvents.CONNECTION_PING, pingData);
|
|
744
|
+
// Если успешно отправлено, обновляем статистику
|
|
745
|
+
if (sent) {
|
|
746
|
+
const stats = this.pingStats.get(namespace);
|
|
747
|
+
if (stats) {
|
|
748
|
+
stats.pingSent++;
|
|
749
|
+
}
|
|
750
|
+
this.logger('debug', `Отправлен ping для ${namespace}`, pingData);
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
this.logger('warn', `Не удалось отправить ping для ${namespace}`);
|
|
754
|
+
}
|
|
755
|
+
// Проверяем таймаут
|
|
756
|
+
const timeouts = this.pingTimeouts.get(namespace) || 0;
|
|
757
|
+
const stats = this.pingStats.get(namespace);
|
|
758
|
+
// Если разница между отправленными и полученными превышает порог,
|
|
759
|
+
// или последний pong был получен слишком давно
|
|
760
|
+
if ((stats && stats.pingSent - stats.pongReceived > this.timeoutThreshold) ||
|
|
761
|
+
(stats && Date.now() - stats.lastPongTimestamp > interval * this.timeoutThreshold)) {
|
|
762
|
+
// Увеличиваем счетчик таймаутов
|
|
763
|
+
this.pingTimeouts.set(namespace, timeouts + 1);
|
|
764
|
+
if (timeouts + 1 >= this.timeoutThreshold) {
|
|
765
|
+
// Соединение потеряно
|
|
766
|
+
this.logger('error', `Соединение потеряно (таймаут ping/pong) для ${namespace}`);
|
|
767
|
+
// Установка флага неактивного соединения
|
|
768
|
+
if (stats) {
|
|
769
|
+
stats.isConnected = false;
|
|
770
|
+
}
|
|
771
|
+
// На прямую отправку события через socket
|
|
772
|
+
this.send(namespace, 'connection_timeout', {
|
|
773
|
+
namespace,
|
|
774
|
+
socketId: client.getSocketId(),
|
|
775
|
+
timeouts: timeouts + 1,
|
|
776
|
+
threshold: this.timeoutThreshold
|
|
777
|
+
});
|
|
778
|
+
// Также вызываем обработчики событий
|
|
779
|
+
const timeoutHandlers = this.pingPongEventHandlers.get('connection_timeout') || [];
|
|
780
|
+
timeoutHandlers.forEach(handler => {
|
|
781
|
+
try {
|
|
782
|
+
handler({
|
|
783
|
+
namespace,
|
|
784
|
+
socketId: client.getSocketId(),
|
|
785
|
+
timeouts: timeouts + 1,
|
|
786
|
+
threshold: this.timeoutThreshold
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
catch (error) {
|
|
790
|
+
this.logger('error', `Ошибка при обработке события connection_timeout`, error);
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}, interval);
|
|
797
|
+
// Сохраняем интервал
|
|
798
|
+
this.pingIntervals.set(namespace, pingInterval);
|
|
799
|
+
this.logger('info', `Включен механизм ping/pong для ${namespace} с интервалом ${interval}ms`);
|
|
800
|
+
}
|
|
801
|
+
catch (error) {
|
|
802
|
+
this.logger('error', `Ошибка при включении ping/pong для ${namespace}`, error);
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return true;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Отключить автоматическую отправку ping-сообщений
|
|
810
|
+
* @param {WebSocketNamespace} [namespace] - Пространство имен для отключения (если не указано - отключается везде)
|
|
811
|
+
*/
|
|
812
|
+
disablePingPong(namespace) {
|
|
813
|
+
if (namespace) {
|
|
814
|
+
// Отключаем для указанного namespace
|
|
815
|
+
const interval = this.pingIntervals.get(namespace);
|
|
816
|
+
if (interval) {
|
|
817
|
+
clearInterval(interval);
|
|
818
|
+
this.pingIntervals.delete(namespace);
|
|
819
|
+
this.logger('info', `Отключен механизм ping/pong для ${namespace}`);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
// Отключаем для всех namespace
|
|
824
|
+
for (const [ns, interval] of this.pingIntervals.entries()) {
|
|
825
|
+
clearInterval(interval);
|
|
826
|
+
this.pingIntervals.delete(ns);
|
|
827
|
+
this.logger('info', `Отключен механизм ping/pong для ${ns}`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Получить статистику ping/pong
|
|
833
|
+
* @param {WebSocketNamespace} [namespace] - Пространство имен для получения статистики
|
|
834
|
+
* @returns {PingPongStats | PingPongStats[] | null} - Статистика ping/pong
|
|
835
|
+
*/
|
|
836
|
+
getPingStats(namespace) {
|
|
837
|
+
if (namespace) {
|
|
838
|
+
// Возвращаем статистику для указанного namespace
|
|
839
|
+
return this.pingStats.get(namespace) || null;
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
// Возвращаем статистику для всех namespace
|
|
843
|
+
return Array.from(this.pingStats.values());
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Добавляет обработчик для событий ping/pong
|
|
848
|
+
* @param {string} eventType - Тип события (connection_timeout)
|
|
849
|
+
* @param {(data: any) => void} handler - Обработчик события
|
|
850
|
+
*/
|
|
851
|
+
onPingPongEvent(eventType, handler) {
|
|
852
|
+
if (!this.pingPongEventHandlers.has(eventType)) {
|
|
853
|
+
this.pingPongEventHandlers.set(eventType, []);
|
|
854
|
+
}
|
|
855
|
+
const handlers = this.pingPongEventHandlers.get(eventType);
|
|
856
|
+
if (handlers) {
|
|
857
|
+
handlers.push(handler);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Удаляет обработчик для событий ping/pong
|
|
862
|
+
* @param {string} eventType - Тип события
|
|
863
|
+
* @param {(data: any) => void} [handler] - Обработчик события (если не указан, удаляются все обработчики)
|
|
864
|
+
*/
|
|
865
|
+
offPingPongEvent(eventType, handler) {
|
|
866
|
+
if (!handler) {
|
|
867
|
+
// Если обработчик не указан, удаляем все обработчики для этого типа события
|
|
868
|
+
this.pingPongEventHandlers.delete(eventType);
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
// Если обработчик указан, удаляем только его
|
|
872
|
+
const handlers = this.pingPongEventHandlers.get(eventType);
|
|
873
|
+
if (handlers) {
|
|
874
|
+
const index = handlers.findIndex(h => h === handler);
|
|
875
|
+
if (index !== -1) {
|
|
876
|
+
handlers.splice(index, 1);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Возвращает функцию-обработчик для pong-ответов, которая рассчитывает RTT
|
|
883
|
+
* @returns {(data: any) => void} Функция-обработчик
|
|
884
|
+
*/
|
|
885
|
+
getPongHandler() {
|
|
886
|
+
return (data) => {
|
|
887
|
+
if (data && data.echo) {
|
|
888
|
+
const rtt = Date.now() - data.echo;
|
|
889
|
+
console.log(`[PONG] RTT: ${rtt}ms, namespace: ${data.namespace || 'unknown'}`);
|
|
890
|
+
return rtt;
|
|
891
|
+
}
|
|
892
|
+
return -1;
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Выполняет диагностику соединения и возвращает подробный отчет
|
|
897
|
+
* @param {WebSocketNamespace} namespace Пространство имен
|
|
898
|
+
* @returns {ConnectionDiagnostics} Объект с диагностической информацией
|
|
899
|
+
*/
|
|
900
|
+
diagnoseConnection(namespace) {
|
|
901
|
+
const client = this.clients.get(namespace);
|
|
902
|
+
const stats = this.pingStats.get(namespace);
|
|
903
|
+
const connectionState = this.getConnectionState(namespace);
|
|
904
|
+
const sessionToken = this.getSessionToken(namespace);
|
|
905
|
+
return {
|
|
906
|
+
namespace,
|
|
907
|
+
isConnected: client?.isConnected() || false,
|
|
908
|
+
socketId: client?.getSocketId() || null,
|
|
909
|
+
lastActivity: stats?.lastPongTimestamp || 0,
|
|
910
|
+
rtt: {
|
|
911
|
+
current: stats?.lastRtt || -1,
|
|
912
|
+
min: stats?.minRtt === Number.MAX_SAFE_INTEGER ? -1 : (stats?.minRtt || -1),
|
|
913
|
+
max: stats?.maxRtt || -1,
|
|
914
|
+
avg: stats?.averageRtt || -1
|
|
915
|
+
},
|
|
916
|
+
pingSent: stats?.pingSent || 0,
|
|
917
|
+
pongReceived: stats?.pongReceived || 0,
|
|
918
|
+
missedPongs: (stats?.pingSent || 0) - (stats?.pongReceived || 0),
|
|
919
|
+
timeoutCount: this.pingTimeouts.get(namespace) || 0,
|
|
920
|
+
reconnectAttempts: connectionState.reconnectAttempts,
|
|
921
|
+
lastConnectTime: connectionState.lastConnectTime,
|
|
922
|
+
sessionRecovery: {
|
|
923
|
+
hasSessionToken: !!sessionToken,
|
|
924
|
+
tokenLength: sessionToken?.length || 0,
|
|
925
|
+
wasRecovered: !!sessionToken && (stats?.pongReceived || 0) > 0
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Выполняет диагностику всех активных соединений
|
|
931
|
+
* @returns {Record<string, ConnectionDiagnostics>} Объект с диагностической информацией по всем соединениям
|
|
932
|
+
*/
|
|
933
|
+
diagnoseAllConnections() {
|
|
934
|
+
const result = {};
|
|
935
|
+
// Проверяем каждое возможное пространство имен
|
|
936
|
+
for (const namespace of Object.values(WebSocketNamespace)) {
|
|
937
|
+
if (this.clients.has(namespace)) {
|
|
938
|
+
result[String(namespace)] = this.diagnoseConnection(namespace);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return result;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Рассчитывает задержку для переподключения на основе количества попыток и стратегии
|
|
945
|
+
* @param {WebSocketNamespace} namespace Пространство имен
|
|
946
|
+
* @returns {number} Задержка в миллисекундах
|
|
947
|
+
*/
|
|
948
|
+
calculateReconnectDelay(namespace) {
|
|
949
|
+
const state = this.getConnectionState(namespace);
|
|
950
|
+
const attempts = state.reconnectAttempts;
|
|
951
|
+
const strategy = this.options.reconnectStrategy || 'exponential';
|
|
952
|
+
const baseDelay = this.options.retryDelay || 1000;
|
|
953
|
+
const maxDelay = this.options.maxRetryDelay || 30000;
|
|
954
|
+
if (strategy === 'exponential') {
|
|
955
|
+
// Экспоненциальный рост с фактором 1.5
|
|
956
|
+
const calculatedDelay = Math.min(baseDelay * Math.pow(1.5, attempts), maxDelay);
|
|
957
|
+
// Добавляем случайный фактор (jitter) для предотвращения штормов переподключений
|
|
958
|
+
return calculatedDelay * (0.8 + Math.random() * 0.4);
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
// Линейный рост
|
|
962
|
+
return Math.min(baseDelay * (attempts + 1), maxDelay);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Принудительно переподключает соединение для указанного пространства имен
|
|
967
|
+
* @param {WebSocketNamespace} namespace Пространство имен
|
|
968
|
+
* @param {boolean} immediate Выполнить переподключение немедленно, без задержки
|
|
969
|
+
* @returns {Promise<boolean>} Успешность операции
|
|
970
|
+
*/
|
|
971
|
+
async reconnectNamespace(namespace, immediate = false) {
|
|
972
|
+
const client = this.clients.get(namespace);
|
|
973
|
+
try {
|
|
974
|
+
// Если клиент уже существует, закрываем его
|
|
975
|
+
if (client) {
|
|
976
|
+
this.logger('info', `Принудительное переподключение для ${namespace}`);
|
|
977
|
+
try {
|
|
978
|
+
// Отключаем ping/pong для этого namespace
|
|
979
|
+
this.disablePingPong(namespace);
|
|
980
|
+
// Закрываем соединение
|
|
981
|
+
client.close();
|
|
982
|
+
}
|
|
983
|
+
catch (e) {
|
|
984
|
+
this.logger('warn', `Ошибка при закрытии соединения с ${namespace}: ${e instanceof Error ? e.message : String(e)}`);
|
|
985
|
+
}
|
|
986
|
+
// Удаляем клиент из кэша
|
|
987
|
+
this.clients.delete(namespace);
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
this.logger('info', `Инициируем новое подключение для ${namespace}`);
|
|
991
|
+
}
|
|
992
|
+
// Устанавливаем состояние переподключения
|
|
993
|
+
this.setConnectionState(namespace, false, true);
|
|
994
|
+
// Инкрементируем счетчик попыток
|
|
995
|
+
this.incrementReconnectAttempts(namespace);
|
|
996
|
+
// Рассчитываем задержку, если не требуется немедленное переподключение
|
|
997
|
+
if (!immediate) {
|
|
998
|
+
const delay = this.calculateReconnectDelay(namespace);
|
|
999
|
+
this.logger('info', `Переподключение для ${namespace} через ${delay}ms (попытка ${this.getConnectionState(namespace).reconnectAttempts})`);
|
|
1000
|
+
// Ждем рассчитанное время
|
|
1001
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1002
|
+
}
|
|
1003
|
+
// Выполняем подключение с соответствующими параметрами
|
|
1004
|
+
let params = {};
|
|
1005
|
+
// Проверяем тип пространства имен и добавляем соответствующие параметры
|
|
1006
|
+
if (namespace === WebSocketNamespace.REASONING && this.activeReasoningId) {
|
|
1007
|
+
params.reasoningId = this.activeReasoningId;
|
|
1008
|
+
}
|
|
1009
|
+
else if ((namespace === WebSocketNamespace.INDEXING || namespace === WebSocketNamespace.DEPENDENCIES) && this.activeProjectId) {
|
|
1010
|
+
params.projectId = this.activeProjectId;
|
|
1011
|
+
}
|
|
1012
|
+
// Подключаемся
|
|
1013
|
+
await this.connect(namespace, params);
|
|
1014
|
+
// Если это пространство имен рассуждений и есть активное рассуждение,
|
|
1015
|
+
// пытаемся присоединиться к нему
|
|
1016
|
+
if (namespace === WebSocketNamespace.REASONING && this.activeReasoningId) {
|
|
1017
|
+
await this.connectToReasoning(this.activeReasoningId);
|
|
1018
|
+
}
|
|
1019
|
+
else if (namespace === WebSocketNamespace.INDEXING && this.activeProjectId) {
|
|
1020
|
+
await this.connectToIndexing(this.activeProjectId);
|
|
1021
|
+
}
|
|
1022
|
+
else if (namespace === WebSocketNamespace.DEPENDENCIES && this.activeProjectId) {
|
|
1023
|
+
await this.connectToDependencies(this.activeProjectId);
|
|
1024
|
+
}
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
catch (error) {
|
|
1028
|
+
this.logger('error', `Ошибка при переподключении к ${namespace}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Настраивает периодическую проверку здоровья соединения
|
|
1034
|
+
* @param {number} [interval=30000] Интервал проверки в миллисекундах
|
|
1035
|
+
*/
|
|
1036
|
+
setupConnectionHealthCheck(interval = 30000) {
|
|
1037
|
+
// Останавливаем существующую проверку, если она есть
|
|
1038
|
+
if (this.healthCheckTimer) {
|
|
1039
|
+
clearInterval(this.healthCheckTimer);
|
|
1040
|
+
}
|
|
1041
|
+
this.healthCheckTimer = setInterval(() => {
|
|
1042
|
+
for (const namespace of Object.values(WebSocketNamespace)) {
|
|
1043
|
+
const typedNamespace = namespace;
|
|
1044
|
+
const client = this.clients.get(typedNamespace);
|
|
1045
|
+
if (!client)
|
|
1046
|
+
continue;
|
|
1047
|
+
// Проверяем соединение через WebSocket клиент
|
|
1048
|
+
if (!client.isConnected()) {
|
|
1049
|
+
this.logger('warn', `Соединение с ${namespace} не активно, инициируем переподключение`);
|
|
1050
|
+
this.reconnectNamespace(typedNamespace, false).catch(() => { });
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
// Проверяем статистику ping/pong
|
|
1054
|
+
const stats = this.pingStats.get(typedNamespace);
|
|
1055
|
+
if (stats) {
|
|
1056
|
+
const now = Date.now();
|
|
1057
|
+
// Если последний pong был получен слишком давно
|
|
1058
|
+
if (now - stats.lastPongTimestamp > interval * 2) {
|
|
1059
|
+
this.logger('warn', `Долгое отсутствие активности для ${namespace}, проверка соединения...`);
|
|
1060
|
+
// Отправляем проверочный ping
|
|
1061
|
+
this.send(typedNamespace, 'connection_health_check', { timestamp: now, echo: now });
|
|
1062
|
+
// Устанавливаем таймаут для проверки ответа
|
|
1063
|
+
setTimeout(() => {
|
|
1064
|
+
const currentStats = this.pingStats.get(typedNamespace);
|
|
1065
|
+
if (currentStats && now - currentStats.lastPongTimestamp > interval * 2) {
|
|
1066
|
+
this.logger('error', `Соединение не отвечает для ${namespace}, инициируем переподключение`);
|
|
1067
|
+
this.reconnectNamespace(typedNamespace, false).catch(() => { });
|
|
1068
|
+
}
|
|
1069
|
+
}, 5000); // Ждем 5 секунд на ответ
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}, interval);
|
|
1074
|
+
this.logger('info', 'Настроена периодическая проверка здоровья соединения с интервалом ' + interval + 'ms');
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Сохраняет токен сессии для пространства имен
|
|
1078
|
+
* @param {WebSocketNamespace} namespace Пространство имен
|
|
1079
|
+
* @param {string} token Токен сессии
|
|
1080
|
+
*/
|
|
1081
|
+
saveSessionToken(namespace, token) {
|
|
1082
|
+
if (this.options.enableSessionPersistence !== false) {
|
|
1083
|
+
this.sessionTokens.set(namespace, token);
|
|
1084
|
+
this.logger('info', `Сохранен токен сессии для ${namespace}`, { tokenLength: token.length });
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Получает сохраненный токен сессии для пространства имен
|
|
1089
|
+
* @param {WebSocketNamespace} namespace Пространство имен
|
|
1090
|
+
* @returns {string | null} Токен сессии или null, если не найден
|
|
1091
|
+
*/
|
|
1092
|
+
getSessionToken(namespace) {
|
|
1093
|
+
if (this.options.enableSessionPersistence === false) {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
return this.sessionTokens.get(namespace) || null;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Удаляет сохраненный токен сессии для пространства имен
|
|
1100
|
+
* @param {WebSocketNamespace} namespace Пространство имен
|
|
1101
|
+
*/
|
|
1102
|
+
clearSessionToken(namespace) {
|
|
1103
|
+
this.sessionTokens.delete(namespace);
|
|
1104
|
+
this.logger('info', `Удален токен сессии для ${namespace}`);
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Устанавливает состояние подключения для пространства имен
|
|
1108
|
+
* @param {WebSocketNamespace} namespace Пространство имен
|
|
1109
|
+
* @param {boolean} connected Состояние подключения
|
|
1110
|
+
* @param {boolean} reconnecting Состояние переподключения
|
|
1111
|
+
*/
|
|
1112
|
+
setConnectionState(namespace, connected, reconnecting = false) {
|
|
1113
|
+
const state = this.getConnectionState(namespace);
|
|
1114
|
+
state.connected = connected;
|
|
1115
|
+
state.reconnecting = reconnecting;
|
|
1116
|
+
if (connected) {
|
|
1117
|
+
state.lastConnectTime = Date.now();
|
|
1118
|
+
state.reconnectAttempts = 0;
|
|
1119
|
+
}
|
|
1120
|
+
this.connectionState.set(namespace, state);
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Увеличивает счетчик попыток переподключения для пространства имен
|
|
1124
|
+
* @param {WebSocketNamespace} namespace Пространство имен
|
|
1125
|
+
* @returns {number} Новое количество попыток
|
|
1126
|
+
*/
|
|
1127
|
+
incrementReconnectAttempts(namespace) {
|
|
1128
|
+
const state = this.getConnectionState(namespace);
|
|
1129
|
+
state.reconnectAttempts++;
|
|
1130
|
+
state.reconnecting = true;
|
|
1131
|
+
this.connectionState.set(namespace, state);
|
|
1132
|
+
return state.reconnectAttempts;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Получает состояние подключения для пространства имен
|
|
1136
|
+
* @param {WebSocketNamespace} namespace Пространство имен
|
|
1137
|
+
* @returns {object} Состояние подключения
|
|
1138
|
+
*/
|
|
1139
|
+
getConnectionState(namespace) {
|
|
1140
|
+
return this.connectionState.get(namespace) || {
|
|
1141
|
+
lastConnectTime: 0,
|
|
1142
|
+
reconnectAttempts: 0,
|
|
1143
|
+
connected: false,
|
|
1144
|
+
reconnecting: false
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Устанавливает ID активной сессии рассуждения
|
|
1149
|
+
* @param {string} reasoningId ID сессии рассуждения
|
|
1150
|
+
* @returns {boolean} Успешность установки
|
|
1151
|
+
*/
|
|
1152
|
+
setActiveReasoningId(reasoningId) {
|
|
1153
|
+
if (!reasoningId) {
|
|
1154
|
+
this.logger('error', 'Попытка установить пустой reasoningId');
|
|
1155
|
+
return false;
|
|
1156
|
+
}
|
|
1157
|
+
this.activeReasoningId = reasoningId;
|
|
1158
|
+
this.logger('info', `Установлен активный reasoningId: ${reasoningId}`);
|
|
1159
|
+
// Если мы уже подключены к пространству имен рассуждений, проверяем, нужно ли присоединиться
|
|
1160
|
+
const client = this.clients.get(WebSocketNamespace.REASONING);
|
|
1161
|
+
if (client && client.isConnected()) {
|
|
1162
|
+
// Пробуем присоединиться к рассуждению асинхронно, но не ждем результата
|
|
1163
|
+
this.logger('debug', `Автоматическое присоединение к рассуждению: ${reasoningId}`);
|
|
1164
|
+
client.emitWithAck(WsEvents.JOIN_REASONING, {
|
|
1165
|
+
reasoningId,
|
|
1166
|
+
token: this.options.apiKey
|
|
1167
|
+
}, 10000).catch(err => {
|
|
1168
|
+
this.logger('warn', `Не удалось автоматически присоединиться к рассуждению: ${err.message}`);
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
return true;
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Устанавливает ID активного проекта
|
|
1175
|
+
* @param {string} projectId ID проекта
|
|
1176
|
+
* @returns {boolean} Успешность установки
|
|
1177
|
+
*/
|
|
1178
|
+
setActiveProjectId(projectId) {
|
|
1179
|
+
if (!projectId) {
|
|
1180
|
+
this.logger('error', 'Попытка установить пустой projectId');
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
this.activeProjectId = projectId;
|
|
1184
|
+
this.logger('info', `Установлен активный projectId: ${projectId}`);
|
|
1185
|
+
return true;
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Устанавливает ID активной сессии рассуждения с расширенными возможностями
|
|
1189
|
+
* @param {string} reasoningId ID сессии рассуждения
|
|
1190
|
+
* @param {boolean} waitForJoin Дождаться результата присоединения
|
|
1191
|
+
* @param {boolean} createIfNotExists Создать новое рассуждение, если ID не существует
|
|
1192
|
+
* @returns {Promise<boolean>} Результат операции
|
|
1193
|
+
*/
|
|
1194
|
+
async setActiveReasoningIdAsync(reasoningId, waitForJoin = false, createIfNotExists = false) {
|
|
1195
|
+
if (!reasoningId && !createIfNotExists) {
|
|
1196
|
+
this.logger('error', 'Попытка установить пустой reasoningId');
|
|
1197
|
+
return false;
|
|
1198
|
+
}
|
|
1199
|
+
// Если указан createIfNotExists и нет reasoningId, создаем новое рассуждение
|
|
1200
|
+
if (createIfNotExists && !reasoningId) {
|
|
1201
|
+
try {
|
|
1202
|
+
reasoningId = await this.createNewReasoning();
|
|
1203
|
+
this.logger('info', `Создано новое рассуждение с ID: ${reasoningId}`);
|
|
1204
|
+
}
|
|
1205
|
+
catch (error) {
|
|
1206
|
+
this.logger('error', 'Не удалось создать новое рассуждение', error);
|
|
1207
|
+
return false;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
// Отключаемся от предыдущего рассуждения, если оно отличается
|
|
1211
|
+
if (this.activeReasoningId && this.activeReasoningId !== reasoningId) {
|
|
1212
|
+
this.logger('debug', `Отписываемся от предыдущего рассуждения: ${this.activeReasoningId}`);
|
|
1213
|
+
this.unsubscribeFromThinking(this.activeReasoningId);
|
|
1214
|
+
}
|
|
1215
|
+
this.activeReasoningId = reasoningId;
|
|
1216
|
+
this.logger('info', `Установлен активный reasoningId: ${reasoningId}`);
|
|
1217
|
+
// Если соединение уже установлено, присоединяемся к рассуждению
|
|
1218
|
+
const client = this.clients.get(WebSocketNamespace.REASONING);
|
|
1219
|
+
if (client && client.isConnected()) {
|
|
1220
|
+
try {
|
|
1221
|
+
if (waitForJoin) {
|
|
1222
|
+
this.logger('debug', `Ожидание присоединения к рассуждению: ${reasoningId}`);
|
|
1223
|
+
const joinResult = await client.emitWithAck(WsEvents.JOIN_REASONING, {
|
|
1224
|
+
reasoningId,
|
|
1225
|
+
token: this.options.apiKey
|
|
1226
|
+
}, 10000);
|
|
1227
|
+
if (joinResult.success === true) {
|
|
1228
|
+
this.logger('info', `Успешно присоединились к рассуждению: ${reasoningId}`);
|
|
1229
|
+
return true;
|
|
1230
|
+
}
|
|
1231
|
+
else {
|
|
1232
|
+
this.logger('warn', `Не удалось присоединиться к рассуждению: ${reasoningId}`, joinResult.error);
|
|
1233
|
+
return false;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
else {
|
|
1237
|
+
// Асинхронное присоединение
|
|
1238
|
+
this.logger('debug', `Асинхронное присоединение к рассуждению: ${reasoningId}`);
|
|
1239
|
+
client.emitWithAck(WsEvents.JOIN_REASONING, {
|
|
1240
|
+
reasoningId,
|
|
1241
|
+
token: this.options.apiKey
|
|
1242
|
+
}, 10000).then(result => {
|
|
1243
|
+
if (result.success === true) {
|
|
1244
|
+
this.logger('info', `Успешно присоединились к рассуждению: ${reasoningId}`);
|
|
1245
|
+
}
|
|
1246
|
+
else {
|
|
1247
|
+
this.logger('warn', `Не удалось присоединиться к рассуждению: ${reasoningId}`, result.error);
|
|
1248
|
+
}
|
|
1249
|
+
}).catch(err => {
|
|
1250
|
+
this.logger('warn', `Ошибка при присоединении к рассуждению: ${reasoningId}`, err);
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
catch (error) {
|
|
1255
|
+
this.logger('error', `Ошибка при присоединении к рассуждению: ${reasoningId}`, error);
|
|
1256
|
+
if (waitForJoin)
|
|
1257
|
+
return false;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
else if (waitForJoin) {
|
|
1261
|
+
// Если ждем присоединения, но соединение отсутствует
|
|
1262
|
+
this.logger('warn', `Нет активного соединения с сервером для присоединения к рассуждению: ${reasoningId}`);
|
|
1263
|
+
return false;
|
|
1264
|
+
}
|
|
1265
|
+
return true;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Создает новое рассуждение на сервере
|
|
1269
|
+
* @private
|
|
1270
|
+
* @returns {Promise<string>} ID нового рассуждения
|
|
1271
|
+
*/
|
|
1272
|
+
async createNewReasoning() {
|
|
1273
|
+
// Подключаемся, если еще не подключены
|
|
1274
|
+
if (!this.isConnectedToReasoning()) {
|
|
1275
|
+
this.logger('debug', 'Подключение к пространству имен рассуждений для создания нового рассуждения');
|
|
1276
|
+
const connected = await this.connectToReasoning();
|
|
1277
|
+
if (!connected) {
|
|
1278
|
+
throw new Error('Не удалось подключиться к пространству имен рассуждений');
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
const client = this.clients.get(WebSocketNamespace.REASONING);
|
|
1282
|
+
if (!client) {
|
|
1283
|
+
throw new Error('Не удалось получить клиент для пространства имен рассуждений');
|
|
1284
|
+
}
|
|
1285
|
+
this.logger('debug', 'Отправка запроса на создание нового рассуждения');
|
|
1286
|
+
const result = await client.emitWithAck(WsEvents.CREATE_REASONING, {
|
|
1287
|
+
token: this.options.apiKey
|
|
1288
|
+
}, 10000);
|
|
1289
|
+
if (!result.reasoningId) {
|
|
1290
|
+
throw new Error(`Сервер не вернул ID рассуждения: ${JSON.stringify(result)}`);
|
|
1291
|
+
}
|
|
1292
|
+
return result.reasoningId;
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Проверяет существование рассуждения на сервере
|
|
1296
|
+
* @param {string} reasoningId ID рассуждения для проверки
|
|
1297
|
+
* @returns {Promise<boolean>} Существует ли рассуждение
|
|
1298
|
+
*/
|
|
1299
|
+
async checkReasoningExists(reasoningId) {
|
|
1300
|
+
if (!reasoningId) {
|
|
1301
|
+
return false;
|
|
1302
|
+
}
|
|
1303
|
+
// Подключаемся, если еще не подключены
|
|
1304
|
+
if (!this.isConnectedToReasoning()) {
|
|
1305
|
+
const connected = await this.connectToReasoning();
|
|
1306
|
+
if (!connected) {
|
|
1307
|
+
this.logger('warn', 'Не удалось подключиться к пространству имен рассуждений для проверки существования');
|
|
1308
|
+
return false;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
const client = this.clients.get(WebSocketNamespace.REASONING);
|
|
1312
|
+
if (!client) {
|
|
1313
|
+
this.logger('warn', 'Не удалось получить клиент для пространства имен рассуждений');
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
try {
|
|
1317
|
+
this.logger('debug', `Проверка существования рассуждения: ${reasoningId}`);
|
|
1318
|
+
// Используем более правильный подход для проверки существования сессии
|
|
1319
|
+
// Пытаемся присоединиться, и если успешно - значит сессия существует
|
|
1320
|
+
const result = await client.emitWithAck(WsEvents.JOIN_REASONING, {
|
|
1321
|
+
reasoningId,
|
|
1322
|
+
token: this.options.apiKey
|
|
1323
|
+
}, 10000);
|
|
1324
|
+
return result.success === true;
|
|
1325
|
+
}
|
|
1326
|
+
catch (error) {
|
|
1327
|
+
this.logger('warn', `Ошибка при проверке существования рассуждения: ${reasoningId}`, error);
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Подключается к пространству имен рассуждений
|
|
1333
|
+
* @param reasoningId ID рассуждения (опционально)
|
|
1334
|
+
* @param options Дополнительные настройки подключения
|
|
1335
|
+
* @returns Promise с результатом подключения
|
|
1336
|
+
*/
|
|
1337
|
+
async connectToReasoning(reasoningId, options = {}) {
|
|
1338
|
+
try {
|
|
1339
|
+
// Значения опций по умолчанию
|
|
1340
|
+
const { autoJoin = true, createIfNotExists = false, checkExistence = false, saveSession = this.options.enableSessionPersistence !== false } = options;
|
|
1341
|
+
this.logger('info', 'Подключение к пространству имен рассуждений', {
|
|
1342
|
+
reasoningId,
|
|
1343
|
+
options: { autoJoin, createIfNotExists, checkExistence, saveSession }
|
|
1344
|
+
});
|
|
1345
|
+
// Если указан ID рассуждения, обрабатываем его
|
|
1346
|
+
if (reasoningId) {
|
|
1347
|
+
// Если нужно проверить существование
|
|
1348
|
+
if (checkExistence) {
|
|
1349
|
+
const client = this.clients.get(WebSocketNamespace.REASONING);
|
|
1350
|
+
// Если уже подключены, проверяем существование
|
|
1351
|
+
if (client && client.isConnected()) {
|
|
1352
|
+
const exists = await this.checkReasoningExists(reasoningId);
|
|
1353
|
+
if (!exists) {
|
|
1354
|
+
if (createIfNotExists) {
|
|
1355
|
+
this.logger('info', `Рассуждение ${reasoningId} не существует, создаем новое`);
|
|
1356
|
+
reasoningId = await this.createNewReasoning();
|
|
1357
|
+
}
|
|
1358
|
+
else {
|
|
1359
|
+
this.logger('warn', `Рассуждение ${reasoningId} не существует`);
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
// Устанавливаем ID активного рассуждения
|
|
1366
|
+
this.activeReasoningId = reasoningId;
|
|
1367
|
+
}
|
|
1368
|
+
else if (createIfNotExists) {
|
|
1369
|
+
// Если нет ID, но нужно создать, создаем новое рассуждение
|
|
1370
|
+
this.logger('info', 'Создание нового рассуждения');
|
|
1371
|
+
try {
|
|
1372
|
+
// Сначала подключаемся к пространству имен
|
|
1373
|
+
const client = await this.connect(WebSocketNamespace.REASONING);
|
|
1374
|
+
// Затем создаем новое рассуждение
|
|
1375
|
+
const newReasoningId = await this.createNewReasoning();
|
|
1376
|
+
this.activeReasoningId = newReasoningId;
|
|
1377
|
+
this.logger('info', `Создано новое рассуждение: ${newReasoningId}`);
|
|
1378
|
+
}
|
|
1379
|
+
catch (error) {
|
|
1380
|
+
this.logger('error', 'Ошибка при создании нового рассуждения', error);
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
// Подключаемся к пространству имен рассуждений
|
|
1385
|
+
const client = await this.connect(WebSocketNamespace.REASONING);
|
|
1386
|
+
// Аутентифицируемся с увеличенным таймаутом
|
|
1387
|
+
try {
|
|
1388
|
+
const authResult = await client.emitWithAck(WsEvents.AUTHENTICATE, {
|
|
1389
|
+
token: this.options.apiKey,
|
|
1390
|
+
reasoningId: this.activeReasoningId
|
|
1391
|
+
}, 10000);
|
|
1392
|
+
this.logger('debug', 'Результат аутентификации в namespace рассуждений', authResult);
|
|
1393
|
+
// Если сервер вернул токен сессии и нужно его сохранить
|
|
1394
|
+
if (saveSession && authResult.sessionToken) {
|
|
1395
|
+
this.saveSessionToken(WebSocketNamespace.REASONING, authResult.sessionToken);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
catch (error) {
|
|
1399
|
+
this.logger('error', 'Ошибка аутентификации в namespace рассуждений', error);
|
|
1400
|
+
return false;
|
|
1401
|
+
}
|
|
1402
|
+
// Если у нас есть ID рассуждения и включено автоматическое присоединение
|
|
1403
|
+
if (this.activeReasoningId && autoJoin) {
|
|
1404
|
+
try {
|
|
1405
|
+
const joinResult = await client.emitWithAck(WsEvents.JOIN_REASONING, {
|
|
1406
|
+
reasoningId: this.activeReasoningId,
|
|
1407
|
+
token: this.options.apiKey
|
|
1408
|
+
}, 10000);
|
|
1409
|
+
this.logger('debug', 'Результат присоединения к рассуждению', joinResult);
|
|
1410
|
+
if (joinResult.success !== true) {
|
|
1411
|
+
this.logger('warn', `Не удалось присоединиться к рассуждению: ${this.activeReasoningId}`, joinResult.error);
|
|
1412
|
+
return false;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
catch (error) {
|
|
1416
|
+
this.logger('error', 'Ошибка присоединения к рассуждению', error);
|
|
1417
|
+
return false;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
// Включаем автоматический ping/pong, если настроено
|
|
1421
|
+
if (this.options.enableAutoPing !== false) {
|
|
1422
|
+
this.enablePingPong(this.options.pingInterval, this.options.pingTimeoutThreshold);
|
|
1423
|
+
}
|
|
1424
|
+
return true;
|
|
1425
|
+
}
|
|
1426
|
+
catch (error) {
|
|
1427
|
+
this.logger('error', 'Ошибка подключения к пространству имен рассуждений', error);
|
|
1428
|
+
return false;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Получает текущий статус сессии рассуждения
|
|
1433
|
+
* @param {string} reasoningId ID сессии рассуждения (опционально, по умолчанию активная)
|
|
1434
|
+
* @returns {Promise<{exists: boolean, isActive: boolean, metadata?: any}>} Статус сессии
|
|
1435
|
+
*/
|
|
1436
|
+
async getReasoningStatus(reasoningId) {
|
|
1437
|
+
const targetId = reasoningId || this.activeReasoningId;
|
|
1438
|
+
if (!targetId) {
|
|
1439
|
+
return { exists: false, isActive: false };
|
|
1440
|
+
}
|
|
1441
|
+
if (!this.isConnectedToReasoning()) {
|
|
1442
|
+
const connected = await this.connectToReasoning(undefined, { autoJoin: false });
|
|
1443
|
+
if (!connected) {
|
|
1444
|
+
this.logger('warn', 'Не удалось подключиться к пространству имен рассуждений');
|
|
1445
|
+
return { exists: false, isActive: false };
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
const client = this.clients.get(WebSocketNamespace.REASONING);
|
|
1449
|
+
if (!client) {
|
|
1450
|
+
return { exists: false, isActive: false };
|
|
1451
|
+
}
|
|
1452
|
+
try {
|
|
1453
|
+
// Используем событие GET_REASONING_STATUS для получения статуса
|
|
1454
|
+
// Если сервер не поддерживает это событие, используем более простую проверку
|
|
1455
|
+
try {
|
|
1456
|
+
const status = await client.emitWithAck('get_reasoning_status', {
|
|
1457
|
+
reasoningId: targetId,
|
|
1458
|
+
token: this.options.apiKey
|
|
1459
|
+
}, 10000);
|
|
1460
|
+
return {
|
|
1461
|
+
exists: status.exists === true,
|
|
1462
|
+
isActive: status.isActive === true,
|
|
1463
|
+
metadata: status.metadata || {}
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
catch (e) {
|
|
1467
|
+
// Если произошла ошибка (скорее всего, сервер не поддерживает этот метод),
|
|
1468
|
+
// используем проверку через JOIN_REASONING
|
|
1469
|
+
const result = await client.emitWithAck(WsEvents.JOIN_REASONING, {
|
|
1470
|
+
reasoningId: targetId,
|
|
1471
|
+
token: this.options.apiKey
|
|
1472
|
+
}, 10000);
|
|
1473
|
+
return {
|
|
1474
|
+
exists: result.success === true,
|
|
1475
|
+
isActive: result.success === true,
|
|
1476
|
+
metadata: result.data
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
catch (error) {
|
|
1481
|
+
this.logger('warn', `Ошибка при получении статуса рассуждения: ${targetId}`, error);
|
|
1482
|
+
return { exists: false, isActive: false };
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Присоединяется к сессии рассуждения
|
|
1487
|
+
* @param {string} reasoningId ID сессии рассуждения
|
|
1488
|
+
* @param {boolean} setActive Установить как активное рассуждение
|
|
1489
|
+
* @returns {Promise<boolean>} Результат операции
|
|
1490
|
+
*/
|
|
1491
|
+
async joinReasoning(reasoningId, setActive = true) {
|
|
1492
|
+
if (!reasoningId) {
|
|
1493
|
+
this.logger('error', 'Попытка присоединиться к пустому reasoningId');
|
|
1494
|
+
return false;
|
|
1495
|
+
}
|
|
1496
|
+
// Если нужно установить как активное, делаем это
|
|
1497
|
+
if (setActive) {
|
|
1498
|
+
this.activeReasoningId = reasoningId;
|
|
1499
|
+
}
|
|
1500
|
+
// Если не подключены, подключаемся сначала
|
|
1501
|
+
if (!this.isConnectedToReasoning()) {
|
|
1502
|
+
const connected = await this.connectToReasoning(reasoningId);
|
|
1503
|
+
if (!connected) {
|
|
1504
|
+
this.logger('warn', `Не удалось подключиться к пространству имен рассуждений для ${reasoningId}`);
|
|
1505
|
+
return false;
|
|
1506
|
+
}
|
|
1507
|
+
return true; // connectToReasoning уже выполнил join
|
|
1508
|
+
}
|
|
1509
|
+
// Если уже подключены, отправляем join запрос
|
|
1510
|
+
const client = this.clients.get(WebSocketNamespace.REASONING);
|
|
1511
|
+
if (!client) {
|
|
1512
|
+
this.logger('warn', 'Не удалось получить клиент для пространства имен рассуждений');
|
|
1513
|
+
return false;
|
|
1514
|
+
}
|
|
1515
|
+
try {
|
|
1516
|
+
const joinResult = await client.emitWithAck(WsEvents.JOIN_REASONING, {
|
|
1517
|
+
reasoningId,
|
|
1518
|
+
token: this.options.apiKey
|
|
1519
|
+
}, 10000);
|
|
1520
|
+
this.logger('debug', `Результат присоединения к рассуждению ${reasoningId}`, joinResult);
|
|
1521
|
+
return joinResult.success === true;
|
|
1522
|
+
}
|
|
1523
|
+
catch (error) {
|
|
1524
|
+
this.logger('error', `Ошибка при присоединении к рассуждению ${reasoningId}`, error);
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Подключается к пространству имен рассуждений с расширенными опциями
|
|
1530
|
+
* @param reasoningId ID рассуждения (опционально)
|
|
1531
|
+
* @param options Дополнительные настройки подключения
|
|
1532
|
+
* @returns Promise с результатом подключения
|
|
1533
|
+
*/
|
|
1534
|
+
async connectToReasoningEx(reasoningId, options = {}) {
|
|
1535
|
+
try {
|
|
1536
|
+
// Значения опций по умолчанию
|
|
1537
|
+
const { autoJoin = true, createIfNotExists = false, checkExistence = false, saveSession = this.options.enableSessionPersistence !== false } = options;
|
|
1538
|
+
this.logger('info', 'Подключение к пространству имен рассуждений', {
|
|
1539
|
+
reasoningId,
|
|
1540
|
+
options: { autoJoin, createIfNotExists, checkExistence, saveSession }
|
|
1541
|
+
});
|
|
1542
|
+
// Если указан ID рассуждения, обрабатываем его
|
|
1543
|
+
if (reasoningId) {
|
|
1544
|
+
// Если нужно проверить существование
|
|
1545
|
+
if (checkExistence) {
|
|
1546
|
+
const client = this.clients.get(WebSocketNamespace.REASONING);
|
|
1547
|
+
// Если уже подключены, проверяем существование
|
|
1548
|
+
if (client && client.isConnected()) {
|
|
1549
|
+
const exists = await this.checkReasoningExists(reasoningId);
|
|
1550
|
+
if (!exists) {
|
|
1551
|
+
if (createIfNotExists) {
|
|
1552
|
+
this.logger('info', `Рассуждение ${reasoningId} не существует, создаем новое`);
|
|
1553
|
+
reasoningId = await this.createNewReasoning();
|
|
1554
|
+
}
|
|
1555
|
+
else {
|
|
1556
|
+
this.logger('warn', `Рассуждение ${reasoningId} не существует`);
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
// Устанавливаем ID активного рассуждения
|
|
1563
|
+
this.activeReasoningId = reasoningId;
|
|
1564
|
+
}
|
|
1565
|
+
else if (createIfNotExists) {
|
|
1566
|
+
// Если нет ID, но нужно создать, создаем новое рассуждение
|
|
1567
|
+
this.logger('info', 'Создание нового рассуждения');
|
|
1568
|
+
try {
|
|
1569
|
+
// Сначала подключаемся к пространству имен
|
|
1570
|
+
const client = await this.connect(WebSocketNamespace.REASONING);
|
|
1571
|
+
// Затем создаем новое рассуждение
|
|
1572
|
+
const newReasoningId = await this.createNewReasoning();
|
|
1573
|
+
this.activeReasoningId = newReasoningId;
|
|
1574
|
+
this.logger('info', `Создано новое рассуждение: ${newReasoningId}`);
|
|
1575
|
+
}
|
|
1576
|
+
catch (error) {
|
|
1577
|
+
this.logger('error', 'Ошибка при создании нового рассуждения', error);
|
|
1578
|
+
return false;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
// Подключаемся к пространству имен рассуждений
|
|
1582
|
+
const client = await this.connect(WebSocketNamespace.REASONING);
|
|
1583
|
+
// Аутентифицируемся с увеличенным таймаутом
|
|
1584
|
+
try {
|
|
1585
|
+
const authResult = await client.emitWithAck(WsEvents.AUTHENTICATE, {
|
|
1586
|
+
token: this.options.apiKey,
|
|
1587
|
+
reasoningId: this.activeReasoningId
|
|
1588
|
+
}, 10000);
|
|
1589
|
+
this.logger('debug', 'Результат аутентификации в namespace рассуждений', authResult);
|
|
1590
|
+
// Если сервер вернул токен сессии и нужно его сохранить
|
|
1591
|
+
if (saveSession && authResult.sessionToken) {
|
|
1592
|
+
this.saveSessionToken(WebSocketNamespace.REASONING, authResult.sessionToken);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
catch (error) {
|
|
1596
|
+
this.logger('error', 'Ошибка аутентификации в namespace рассуждений', error);
|
|
1597
|
+
return false;
|
|
1598
|
+
}
|
|
1599
|
+
// Если у нас есть ID рассуждения и включено автоматическое присоединение
|
|
1600
|
+
if (this.activeReasoningId && autoJoin) {
|
|
1601
|
+
try {
|
|
1602
|
+
const joinResult = await client.emitWithAck(WsEvents.JOIN_REASONING, {
|
|
1603
|
+
reasoningId: this.activeReasoningId,
|
|
1604
|
+
token: this.options.apiKey
|
|
1605
|
+
}, 10000);
|
|
1606
|
+
this.logger('debug', 'Результат присоединения к рассуждению', joinResult);
|
|
1607
|
+
if (joinResult.success !== true) {
|
|
1608
|
+
this.logger('warn', `Не удалось присоединиться к рассуждению: ${this.activeReasoningId}`, joinResult.error);
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
catch (error) {
|
|
1613
|
+
this.logger('error', 'Ошибка присоединения к рассуждению', error);
|
|
1614
|
+
return false;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
// Включаем автоматический ping/pong, если настроено
|
|
1618
|
+
if (this.options.enableAutoPing !== false) {
|
|
1619
|
+
this.enablePingPong(this.options.pingInterval, this.options.pingTimeoutThreshold);
|
|
1620
|
+
}
|
|
1621
|
+
return true;
|
|
1622
|
+
}
|
|
1623
|
+
catch (error) {
|
|
1624
|
+
this.logger('error', 'Ошибка подключения к пространству имен рассуждений', error);
|
|
1625
|
+
return false;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
698
1628
|
}
|
|
699
1629
|
//# sourceMappingURL=code-solver-websocket-client.js.map
|