solver-sdk 10.4.8 → 10.5.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.
@@ -10,9 +10,31 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
10
10
  if (k2 === undefined) k2 = k;
11
11
  o[k2] = m[k];
12
12
  }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
13
18
  var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
19
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
20
  };
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
16
38
  Object.defineProperty(exports, "__esModule", { value: true });
17
39
  exports.ChatApi = void 0;
18
40
  const stream_utils_1 = require("./stream-utils");
@@ -36,6 +58,10 @@ class PersistentSSEConnectionImpl {
36
58
  this.eventHandlers = new Map();
37
59
  this.reconnectAttempts = 0;
38
60
  this.maxReconnectAttempts = 3;
61
+ // 🔧 WorkAI: Chat events queue for persistent connection
62
+ this.chatEventsQueue = [];
63
+ this.chatEventsResolvers = [];
64
+ this.chatEventsCompleted = false;
39
65
  this.id = id;
40
66
  this.sessionId = sessionId;
41
67
  this.closeCallback = closeCallback;
@@ -121,6 +147,52 @@ class PersistentSSEConnectionImpl {
121
147
  });
122
148
  }
123
149
  }
150
+ // 🔧 WorkAI: Queue chat event for async generator
151
+ queueChatEvent(event) {
152
+ if (this.chatEventsResolvers.length > 0) {
153
+ // Someone is waiting - resolve immediately
154
+ const resolver = this.chatEventsResolvers.shift();
155
+ resolver(event);
156
+ }
157
+ else {
158
+ // Queue for later
159
+ this.chatEventsQueue.push(event);
160
+ }
161
+ // Also emit for listeners
162
+ this.emit('chat_event', event);
163
+ }
164
+ // 🔧 WorkAI: Stream chat events as async generator
165
+ async *streamChatEvents() {
166
+ while (!this.chatEventsCompleted || this.chatEventsQueue.length > 0) {
167
+ // Check queue first
168
+ if (this.chatEventsQueue.length > 0) {
169
+ yield this.chatEventsQueue.shift();
170
+ continue;
171
+ }
172
+ // Wait for next event
173
+ const event = await new Promise((resolve) => {
174
+ if (this.chatEventsCompleted) {
175
+ resolve(null);
176
+ }
177
+ else {
178
+ this.chatEventsResolvers.push(resolve);
179
+ }
180
+ });
181
+ if (event === null) {
182
+ break;
183
+ }
184
+ yield event;
185
+ }
186
+ }
187
+ // 🔧 WorkAI: Mark chat events stream as complete
188
+ completeChatEvents() {
189
+ this.chatEventsCompleted = true;
190
+ // Resolve all pending resolvers with null
191
+ while (this.chatEventsResolvers.length > 0) {
192
+ const resolver = this.chatEventsResolvers.shift();
193
+ resolver(null);
194
+ }
195
+ }
124
196
  }
125
197
  /**
126
198
  * API для работы с чатом с поддержкой Anthropic Extended Thinking
@@ -189,6 +261,52 @@ class ChatApi extends cancel_methods_1.ChatCancelMethods {
189
261
  };
190
262
  this.options = options;
191
263
  }
264
+ /**
265
+ * 🔧 WorkAI: Выполняет streaming GET запрос используя нативный fetch
266
+ * Используется для persistent SSE connections, где axios не работает
267
+ *
268
+ * @param url - Полный URL для запроса
269
+ * @param headers - HTTP заголовки
270
+ * @returns Promise<Response> с ReadableStream body
271
+ *
272
+ * Best practice from undici docs: Node 18+ has global fetch
273
+ */
274
+ async fetchStream(url, headers) {
275
+ console.log(`[SDK] 🔌 fetchStream: Attempting fetch to ${url}`);
276
+ console.log(`[SDK] 🔌 fetchStream: globalThis.fetch available: ${typeof globalThis.fetch !== 'undefined'}`);
277
+ // Node 18+ has native fetch support
278
+ if (typeof globalThis.fetch !== 'undefined') {
279
+ console.log(`[SDK] 🔌 fetchStream: Using globalThis.fetch`);
280
+ try {
281
+ const response = await globalThis.fetch(url, {
282
+ method: 'GET',
283
+ headers
284
+ });
285
+ console.log(`[SDK] ✅ fetchStream: Response received, status: ${response.status}, hasBody: ${!!response.body}`);
286
+ return response;
287
+ }
288
+ catch (error) {
289
+ console.error(`[SDK] ❌ fetchStream: globalThis.fetch failed:`, error.message);
290
+ throw error;
291
+ }
292
+ }
293
+ // Fallback для Node 16 через dynamic import
294
+ console.log(`[SDK] 🔌 fetchStream: globalThis.fetch not available, trying node-fetch`);
295
+ try {
296
+ const { default: nodeFetch } = await Promise.resolve().then(() => __importStar(require('node-fetch')));
297
+ console.log(`[SDK] ✅ fetchStream: node-fetch imported`);
298
+ const response = await nodeFetch(url, {
299
+ method: 'GET',
300
+ headers
301
+ });
302
+ console.log(`[SDK] ✅ fetchStream: node-fetch response received, status: ${response.status}`);
303
+ return response;
304
+ }
305
+ catch (error) {
306
+ console.error(`[SDK] ❌ fetchStream: node-fetch failed:`, error.message);
307
+ throw new Error(`Fetch API unavailable: ${error.message}. Please use Node.js 18+ or install node-fetch`);
308
+ }
309
+ }
192
310
  /**
193
311
  * Отправляет сообщение в чат и получает ответ от модели
194
312
  * @param {ChatMessage[]} messages Массив сообщений для отправки
@@ -926,139 +1044,143 @@ class ChatApi extends cancel_methods_1.ChatCancelMethods {
926
1044
  if (!clientRequestId) {
927
1045
  throw new Error('clientRequestId is required for subscribeToResponse');
928
1046
  }
929
- // 🔧 WorkAI: If persistent connection provided, use it instead
1047
+ // 🔧 WorkAI: If persistent connection provided and connected, yield events from it
930
1048
  if (options.persistentConnection && options.persistentConnection.isConnected) {
931
1049
  this.logger.info(`🔌 [PRE_SUBSCRIBE] Using persistent connection: ${clientRequestId}`);
932
- // TODO: Yield events from persistent connection
933
- // For now, fallback to direct subscription
934
- this.logger.warn(`⚠️ [PRE_SUBSCRIBE] Persistent connection event streaming not yet implemented`);
1050
+ // Call onSubscribed callback immediately since connection already exists
1051
+ if (options.onSubscribed) {
1052
+ options.onSubscribed();
1053
+ }
1054
+ // Stream events from persistent connection
1055
+ try {
1056
+ for await (const event of options.persistentConnection.streamChatEvents()) {
1057
+ if (event === null) {
1058
+ break; // Stream completed
1059
+ }
1060
+ // Events from persistent connection are already in ChatStreamChunk format
1061
+ yield event;
1062
+ }
1063
+ this.logger.info(`🔌 [PERSISTENT_STREAM] Completed streaming from persistent connection`);
1064
+ return; // Successfully streamed from persistent connection
1065
+ }
1066
+ catch (error) {
1067
+ // 🚨 КРИТИЧНО: ошибка streaming из persistent connection
1068
+ this.logger.error(`❌ [PERSISTENT_STREAM] CRITICAL ERROR streaming from persistent connection: ${error.message} | ` +
1069
+ `stack=${error.stack} | clientRequestId=${clientRequestId} | connectionId=${options.persistentConnection.id}`);
1070
+ // 🚨 НЕ ДЕЛАЕМ FALLBACK! Если persistent connection сломан - это должно быть видно!
1071
+ throw new Error(`Persistent SSE stream failed: ${error.message}. ` +
1072
+ `This indicates a problem with persistent connection lifecycle. ` +
1073
+ `Check backend routing and connection management.`);
1074
+ }
935
1075
  }
936
1076
  const endpoint = `/api/v1/chat/subscribe?clientRequestId=${encodeURIComponent(clientRequestId)}`;
1077
+ // 🚨 КРИТИЧНО: Если дошли сюда БЕЗ persistent connection - это проблема!
1078
+ this.logger.error(`❌ [PRE_SUBSCRIBE] FALLBACK to regular /subscribe endpoint! ` +
1079
+ `This means persistent connection is NOT working. ` +
1080
+ `Multiple continuations WILL fail with "No response body" errors.`);
937
1081
  this.logger.info(`🔌 [PRE_SUBSCRIBE] Connecting to SSE: ${clientRequestId}`);
938
- // 🔧 WorkAI: Retry logic for "No response body" errors
939
- let lastError = null;
940
- const maxRetries = 1;
941
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
942
- if (attempt > 0) {
943
- this.logger.warn(`🔄 [RETRY_SUBSCRIBE] Attempt ${attempt + 1}/${maxRetries + 1} after 500ms`);
944
- await new Promise(resolve => setTimeout(resolve, 500));
1082
+ try {
1083
+ // Используем request с responseType='stream' для GET запроса с SSE
1084
+ const response = await this.httpClient.request({
1085
+ method: 'GET',
1086
+ url: endpoint,
1087
+ headers: {
1088
+ 'X-Project-ID': options.projectId,
1089
+ 'X-Client-Ready': 'true',
1090
+ },
1091
+ responseType: 'stream',
1092
+ });
1093
+ // 🔍 DEBUG: Log response details
1094
+ this.logger.debug(`🔌 [PRE_SUBSCRIBE] Response status: ${response.status}`);
1095
+ this.logger.debug(`🔌 [PRE_SUBSCRIBE] Response headers: ${JSON.stringify(response.headers)}`);
1096
+ this.logger.debug(`🔌 [PRE_SUBSCRIBE] Response body present: ${!!response.body}`);
1097
+ if (!response.body) {
1098
+ this.logger.error(`❌ [PRE_SUBSCRIBE] No response body! Full response: ${JSON.stringify({
1099
+ status: response.status,
1100
+ statusText: response.statusText,
1101
+ headers: response.headers,
1102
+ bodyType: typeof response.body,
1103
+ })}`);
1104
+ throw new Error('No response body from subscribe endpoint');
945
1105
  }
1106
+ const reader = response.body.getReader();
1107
+ const decoder = new TextDecoder();
1108
+ let subscribed = false;
1109
+ // 🔧 FIX: Буфер для накопления неполных SSE строк
1110
+ let lineBuffer = '';
946
1111
  try {
947
- // Используем request с responseType='stream' для GET запроса с SSE
948
- const response = await this.httpClient.request({
949
- method: 'GET',
950
- url: endpoint,
951
- headers: {
952
- 'X-Project-ID': options.projectId,
953
- 'X-Client-Ready': 'true',
954
- },
955
- responseType: 'stream',
956
- });
957
- // 🔍 DEBUG: Log response details
958
- this.logger.debug(`🔌 [PRE_SUBSCRIBE] Response status: ${response.status}`);
959
- this.logger.debug(`🔌 [PRE_SUBSCRIBE] Response headers: ${JSON.stringify(response.headers)}`);
960
- this.logger.debug(`🔌 [PRE_SUBSCRIBE] Response body present: ${!!response.body}`);
961
- if (!response.body) {
962
- this.logger.error(`❌ [PRE_SUBSCRIBE] No response body! Full response: ${JSON.stringify({
963
- status: response.status,
964
- statusText: response.statusText,
965
- headers: response.headers,
966
- bodyType: typeof response.body,
967
- })}`);
968
- throw new Error('No response body from subscribe endpoint');
969
- }
970
- const reader = response.body.getReader();
971
- const decoder = new TextDecoder();
972
- let subscribed = false;
973
- // 🔧 FIX: Буфер для накопления неполных SSE строк
974
- let lineBuffer = '';
975
- try {
976
- while (true) {
977
- const { done, value } = await reader.read();
978
- if (done) {
979
- this.logger.info(`🔌 [PRE_SUBSCRIBE] SSE stream ended: ${clientRequestId}`);
980
- break;
981
- }
982
- const chunk = decoder.decode(value, { stream: true });
983
- // 🔧 FIX: Добавляем к буферу, а не парсим сразу
984
- lineBuffer += chunk;
985
- // Ищем полные строки (заканчиваются на \n)
986
- const lines = lineBuffer.split('\n');
987
- // 🔧 FIX: Последняя "строка" может быть неполной - сохраняем в буфер
988
- lineBuffer = lines.pop() || '';
989
- for (const line of lines) {
990
- if (!line.trim() || !line.startsWith('data: '))
991
- continue;
992
- const data = line.slice(6).trim();
993
- if (!data)
994
- continue;
995
- try {
996
- const event = JSON.parse(data);
997
- // Callback when subscribed
998
- if (event.type === 'subscribed' && !subscribed) {
999
- subscribed = true;
1000
- this.logger.info(`🔌 [PRE_SUBSCRIBE] Subscribed successfully: ${clientRequestId}`);
1001
- if (options.onSubscribed) {
1002
- options.onSubscribed();
1003
- }
1004
- }
1005
- // Skip heartbeat events
1006
- if (event.type === 'heartbeat') {
1007
- continue;
1008
- }
1009
- // Timeout event
1010
- if (event.type === 'timeout') {
1011
- this.logger.warn(`🔌 [PRE_SUBSCRIBE] Timeout: ${clientRequestId}`);
1012
- break;
1013
- }
1014
- // Convert to ChatStreamChunk and yield
1015
- const streamChunk = {
1016
- type: event.type,
1017
- ...event,
1018
- };
1019
- yield streamChunk;
1020
- // End on message_stop
1021
- if (event.type === 'message_stop') {
1022
- this.logger.info(`🔌 [PRE_SUBSCRIBE] Message stop received: ${clientRequestId}`);
1023
- break;
1112
+ while (true) {
1113
+ const { done, value } = await reader.read();
1114
+ if (done) {
1115
+ this.logger.info(`🔌 [PRE_SUBSCRIBE] SSE stream ended: ${clientRequestId}`);
1116
+ break;
1117
+ }
1118
+ const chunk = decoder.decode(value, { stream: true });
1119
+ // 🔧 FIX: Добавляем к буферу, а не парсим сразу
1120
+ lineBuffer += chunk;
1121
+ // Ищем полные строки (заканчиваются на \n)
1122
+ const lines = lineBuffer.split('\n');
1123
+ // 🔧 FIX: Последняя "строка" может быть неполной - сохраняем в буфер
1124
+ lineBuffer = lines.pop() || '';
1125
+ for (const line of lines) {
1126
+ if (!line.trim() || !line.startsWith('data: '))
1127
+ continue;
1128
+ const data = line.slice(6).trim();
1129
+ if (!data)
1130
+ continue;
1131
+ try {
1132
+ const event = JSON.parse(data);
1133
+ // Callback when subscribed
1134
+ if (event.type === 'subscribed' && !subscribed) {
1135
+ subscribed = true;
1136
+ this.logger.info(`🔌 [PRE_SUBSCRIBE] Subscribed successfully: ${clientRequestId}`);
1137
+ if (options.onSubscribed) {
1138
+ options.onSubscribed();
1024
1139
  }
1025
1140
  }
1026
- catch (parseError) {
1027
- this.logger.warn(`🔌 [PRE_SUBSCRIBE] Parse error: ${parseError}`);
1141
+ // Skip heartbeat events
1142
+ if (event.type === 'heartbeat') {
1143
+ continue;
1144
+ }
1145
+ // Timeout event
1146
+ if (event.type === 'timeout') {
1147
+ this.logger.warn(`🔌 [PRE_SUBSCRIBE] Timeout: ${clientRequestId}`);
1148
+ break;
1028
1149
  }
1150
+ // Convert to ChatStreamChunk and yield
1151
+ const streamChunk = {
1152
+ type: event.type,
1153
+ ...event,
1154
+ };
1155
+ yield streamChunk;
1156
+ // End on message_stop
1157
+ if (event.type === 'message_stop') {
1158
+ this.logger.info(`🔌 [PRE_SUBSCRIBE] Message stop received: ${clientRequestId}`);
1159
+ break;
1160
+ }
1161
+ }
1162
+ catch (parseError) {
1163
+ this.logger.warn(`🔌 [PRE_SUBSCRIBE] Parse error: ${parseError}`);
1029
1164
  }
1030
1165
  }
1031
1166
  }
1032
- finally {
1033
- reader.releaseLock();
1034
- }
1035
- // Success - break retry loop
1036
- return;
1037
1167
  }
1038
- catch (error) {
1039
- lastError = error;
1040
- // 🔌 WorkAI: Graceful handling для stream abort
1041
- if (error.name === 'StreamAbortedError' ||
1042
- (error.message && error.message.includes('aborted'))) {
1043
- this.logger.info(`🔄 [PRE_SUBSCRIBE] SSE stream aborted (likely replaced): ${clientRequestId}`);
1044
- // НЕ выбрасываем ошибку - это нормальное завершение
1045
- return;
1046
- }
1047
- // 🔄 WorkAI: Retry for "No response body" errors
1048
- if (error.message && error.message.includes('No response body')) {
1049
- if (attempt < maxRetries) {
1050
- this.logger.warn(`⚠️ [PRE_SUBSCRIBE] No response body, will retry: ${clientRequestId}`);
1051
- continue; // Try again
1052
- }
1053
- }
1054
- this.logger.error(`🔌 [PRE_SUBSCRIBE] Error: ${error.message}`);
1055
- throw error;
1168
+ finally {
1169
+ reader.releaseLock();
1056
1170
  }
1057
1171
  }
1058
- // If we exhausted all retries
1059
- if (lastError) {
1060
- this.logger.error(`❌ [PRE_SUBSCRIBE] All ${maxRetries + 1} attempts failed: ${lastError.message}`);
1061
- throw lastError;
1172
+ catch (error) {
1173
+ // 🚨 КРИТИЧНО: Ошибка при fallback на /subscribe
1174
+ this.logger.error(`❌ [PRE_SUBSCRIBE] Failed to subscribe to /subscribe endpoint: ${error.message} | ` +
1175
+ `stack=${error.stack} | clientRequestId=${clientRequestId}`);
1176
+ // 🔌 WorkAI: Graceful handling только для stream abort
1177
+ if (error.name === 'StreamAbortedError' ||
1178
+ (error.message && error.message.includes('aborted'))) {
1179
+ this.logger.info(`🔄 [PRE_SUBSCRIBE] SSE stream aborted (likely replaced): ${clientRequestId}`);
1180
+ return; // Это нормально
1181
+ }
1182
+ // Для всех остальных ошибок - прокидываем наверх
1183
+ throw error;
1062
1184
  }
1063
1185
  }
1064
1186
  /**
@@ -1076,45 +1198,93 @@ class ChatApi extends cancel_methods_1.ChatCancelMethods {
1076
1198
  }
1077
1199
  });
1078
1200
  try {
1079
- const response = await this.httpClient.request({
1080
- method: 'GET',
1081
- url: endpoint,
1082
- headers: { 'X-Project-ID': options.projectId },
1083
- responseType: 'stream',
1084
- });
1085
- if (!response.body)
1201
+ // 🔧 WorkAI: Получаем auth token динамически для 401 retry support
1202
+ console.log(`[SDK] 🔌 openPersistentSSE: Getting auth token...`);
1203
+ let authToken = null;
1204
+ if (typeof this.httpClient.options?.getAuthToken === 'function') {
1205
+ try {
1206
+ authToken = await Promise.resolve(this.httpClient.options.getAuthToken());
1207
+ console.log(`[SDK] ✅ openPersistentSSE: Auth token obtained`);
1208
+ }
1209
+ catch (error) {
1210
+ console.log(`[SDK] ⚠️ openPersistentSSE: Failed to get auth token: ${error.message}`);
1211
+ this.logger.warn(`⚠️ [PERSISTENT_SSE] Failed to get auth token: ${error.message}`);
1212
+ }
1213
+ }
1214
+ // Формируем headers с auth token
1215
+ const headers = {
1216
+ 'X-Project-ID': options.projectId,
1217
+ };
1218
+ if (authToken) {
1219
+ headers['Authorization'] = `Bearer ${authToken}`;
1220
+ }
1221
+ // Формируем полный URL (baseURL + endpoint)
1222
+ const baseURL = this.httpClient.getBaseURL?.() || 'http://localhost:3000';
1223
+ const fullUrl = `${baseURL}${endpoint}`;
1224
+ console.log(`[SDK] 🔌 openPersistentSSE: Calling fetchStream to ${fullUrl}`);
1225
+ // 🔧 WorkAI: Используем нативный fetch вместо axios для streaming
1226
+ // Best practice: fetch нативно возвращает Response с ReadableStream body
1227
+ const response = await this.fetchStream(fullUrl, headers);
1228
+ // Проверка статуса (401 retry будет добавлен отдельно если потребуется)
1229
+ console.log(`[SDK] 📡 openPersistentSSE: Response status: ${response.status}`);
1230
+ if (!response.ok) {
1231
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1232
+ }
1233
+ if (!response.body) {
1234
+ console.error(`[SDK] ❌ openPersistentSSE: No response body!`);
1086
1235
  throw new Error('No response body from persistent SSE');
1236
+ }
1237
+ console.log(`[SDK] ✅ openPersistentSSE: Response body available, starting SSE parsing...`);
1238
+ // 🔧 SSE parsing согласно best practices (ofetch + undici)
1087
1239
  const reader = response.body.getReader();
1088
1240
  const decoder = new TextDecoder();
1089
1241
  let buffer = '';
1242
+ // Асинхронно читаем stream
1090
1243
  (async () => {
1091
1244
  try {
1092
1245
  while (true) {
1093
1246
  const { done, value } = await reader.read();
1094
1247
  if (done)
1095
1248
  break;
1249
+ // Декодируем с stream: true для поддержки multi-byte символов
1096
1250
  buffer += decoder.decode(value, { stream: true });
1097
1251
  const lines = buffer.split('\n');
1252
+ // Оставляем неполную строку в buffer (best practice)
1098
1253
  buffer = lines.pop() || '';
1099
1254
  for (const line of lines) {
1100
1255
  if (line.startsWith('data: ')) {
1101
1256
  try {
1102
1257
  const event = JSON.parse(line.slice(6));
1103
- if (event.type === 'persistent_ready')
1258
+ // Handle connection lifecycle events
1259
+ if (event.type === 'persistent_ready') {
1104
1260
  connection.markReady();
1105
- else if (event.type === 'ping')
1261
+ }
1262
+ else if (event.type === 'ping') {
1106
1263
  connection.updatePing(event.timestamp);
1264
+ }
1107
1265
  else if (event.type === 'connection_closed') {
1108
1266
  connection.isConnected = false;
1267
+ connection.completeChatEvents();
1109
1268
  break;
1110
1269
  }
1270
+ // 🔧 WorkAI: Queue chat events (message_start, content_block_delta, etc.)
1271
+ else if (event.type && event.type.startsWith('message_') ||
1272
+ event.type === 'content_block_start' ||
1273
+ event.type === 'content_block_delta' ||
1274
+ event.type === 'content_block_stop' ||
1275
+ event.type === 'error') {
1276
+ connection.queueChatEvent(event);
1277
+ }
1278
+ }
1279
+ catch (e) {
1280
+ // Ignore JSON parse errors for non-JSON SSE events
1111
1281
  }
1112
- catch (e) { }
1113
1282
  }
1114
1283
  }
1115
1284
  }
1116
1285
  }
1117
1286
  catch (error) {
1287
+ // Ignore abort errors (normal when connection closes)
1118
1288
  if (!error.message?.includes('aborted')) {
1119
1289
  connection.emit('error', error);
1120
1290
  connection.markFailed(error);
@@ -1124,9 +1294,10 @@ class ChatApi extends cancel_methods_1.ChatCancelMethods {
1124
1294
  reader.releaseLock();
1125
1295
  }
1126
1296
  })();
1297
+ // Ждем persistent_ready event с timeout
1127
1298
  await Promise.race([
1128
1299
  connection.waitForReady(),
1129
- new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 10000)),
1300
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Persistent SSE timeout after 10s')), 10000)),
1130
1301
  ]);
1131
1302
  return connection;
1132
1303
  }