UNPKG

solver-sdk

Version:

SDK для интеграции с Code Solver Backend API

388 lines 19.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ChatApi = void 0; const websocket_helpers_1 = require("./websocket-helpers"); const stream_utils_1 = require("./stream-utils"); // Экспортируем все типы и интерфейсы для внешнего использования __exportStar(require("./models"), exports); __exportStar(require("./interfaces"), exports); /** * API для работы с чатом */ class ChatApi { /** * Создает новый экземпляр API для работы с чатом * @param {IHttpClient} httpClient HTTP клиент */ constructor(httpClient) { this.httpClient = httpClient; } /** * Отправляет сообщение в чат и получает ответ от модели * @param {ChatMessage[]} messages Массив сообщений для отправки * @param {ChatOptions} [options] Дополнительные параметры * @returns {Promise<ChatResponse>} Ответ модели */ async chat(messages, options) { if (!messages || messages.length === 0) { throw new Error('Необходимо предоставить хотя бы одно сообщение'); } // Проверяем наличие хотя бы одного сообщения от пользователя const hasUserMessage = messages.some(msg => msg.role === 'user'); if (!hasUserMessage) { throw new Error('В сообщениях должно быть хотя бы одно сообщение с ролью "user"'); } // Отправляем запрос к API чата return this.httpClient.post('/api/v1/chat', { model: options?.model || 'Claude', messages, temperature: options?.temperature, maxTokens: options?.maxTokens, stopSequences: options?.stopSequences, functions: options?.functions, functionCall: options?.functionCall, thinking: options?.thinking, region: options?.region }); } /** * Алиас для метода chat для совместимости с другими SDK * @param {ChatMessage[]} messages Массив сообщений для отправки * @param {ChatOptions} [options] Дополнительные параметры * @returns {Promise<ChatResponse>} Ответ модели */ async chatCompletion(messages, options) { return this.chat(messages, options); } /** * Проверяет доступность API чата * @returns {Promise<boolean>} Результат проверки */ async checkAvailability() { try { // Используем HEAD запрос вместо GET, так как эндпоинт поддерживает только HEAD/POST await this.httpClient.request({ method: 'HEAD', url: '/api/v1/chat' }); return true; } catch (error) { return false; } } /** * Отправляет сообщение в чат и получает ответ от модели * с автоматическим переключением между регионами при ошибках перегрузки * @param {ChatMessage[]} messages Массив сообщений для отправки * @param {ChatOptions} [options] Дополнительные параметры * @returns {Promise<ChatResponse>} Ответ модели */ async chatWithRegionFailover(messages, options) { // Список всех доступных регионов const allRegions = ['us-east-1', 'eu-west-1', 'ap-southeast-2']; // Начинаем с региона из параметров, или первого в списке let startRegionIndex = 0; if (options?.region) { const regionIndex = allRegions.indexOf(options.region); if (regionIndex !== -1) { startRegionIndex = regionIndex; } } // Реорганизуем массив, чтобы начать с указанного региона const regions = [ ...allRegions.slice(startRegionIndex), ...allRegions.slice(0, startRegionIndex) ]; // Последняя ошибка, будет возвращена если все регионы недоступны let lastError = null; // Пробуем каждый регион по очереди for (let i = 0; i < regions.length; i++) { const region = regions[i]; try { console.log(`Попытка запроса к Anthropic API в регионе ${region}`); // Копируем опции и устанавливаем текущий регион const regionOptions = { ...options, region }; // Отправляем запрос с конкретным регионом return await this.chat(messages, regionOptions); } catch (error) { lastError = error; // Проверяем, является ли ошибка ошибкой перегрузки (код 529) const isOverloadError = error.status === 529 || error.code === 529 || (error.response?.status === 529) || (error.message && error.message.includes('overloaded')) || (error.error?.type === 'overloaded_error'); if (isOverloadError) { console.warn(`Регион ${region} перегружен, пробуем следующий регион`); // Продолжаем цикл и пробуем следующий регион continue; } else { // Если ошибка не связана с перегрузкой, прекращаем попытки console.error(`Ошибка в регионе ${region}, не связанная с перегрузкой:`, error); throw error; } } } // Если мы здесь, значит все регионы перегружены throw lastError || new Error('Все регионы Anthropic API перегружены, попробуйте позже'); } /** * Отправляет одиночный запрос к модели с автоматическим переключением регионов * @param {string} prompt Запрос к модели * @param {ChatOptions} [options] Дополнительные параметры * @returns {Promise<string>} Текстовый ответ модели */ async sendPromptWithRegionFailover(prompt, options) { const messages = [ { role: 'user', content: prompt } ]; const response = await this.chatWithRegionFailover(messages, options); if (response.choices && response.choices.length > 0) { return response.choices[0].message.content; } throw new Error('Модель не вернула ответ'); } /** * Отправляет сообщение в чат в потоковом режиме * @param {ChatMessage[]} messages Массив сообщений для отправки * @param {ChatStreamOptions} [options] Дополнительные параметры * @returns {AsyncGenerator<ChatStreamChunk>} Асинхронный генератор чанков ответа */ async *streamChat(messages, options) { if (!messages || messages.length === 0) { throw new Error('Необходимо предоставить хотя бы одно сообщение'); } // Проверяем наличие хотя бы одного сообщения от пользователя const hasUserMessage = messages.some(msg => msg.role === 'user'); if (!hasUserMessage) { throw new Error('В сообщениях должно быть хотя бы одно сообщение с ролью "user"'); } try { // Создаем параметры запроса const params = { model: options?.model || 'Claude', messages, temperature: options?.temperature, maxTokens: options?.maxTokens, stopSequences: options?.stopSequences, functions: options?.functions, functionCall: options?.functionCall, thinking: options?.thinking, stream: true }; // Получаем поток данных от API const response = await this.httpClient.request({ url: '/api/v1/chat/stream', method: 'POST', data: params, headers: { 'Accept': 'text/event-stream', 'Cache-Control': 'no-cache' } }); if (!response || !response.body) { throw new Error('Сервер не вернул поток данных'); } // Обрабатываем поток событий const reader = response.body.getReader(); const decoder = new TextDecoder(); let isInThinkingBlock = false; let thinkingContent = ''; let textContent = ''; let thinkingSignature = ''; while (true) { const { done, value } = await reader.read(); if (done) { break; } const chunk = decoder.decode(value); const lines = chunk.split('\n').filter(line => line.trim() !== ''); for (const line of lines) { if (!line.startsWith('data:')) continue; const result = (0, stream_utils_1.processStreamChunk)(line, isInThinkingBlock, thinkingContent, textContent, thinkingSignature, options?.onToken); isInThinkingBlock = result.isInThinkingBlock; thinkingContent = result.thinkingContent; textContent = result.textContent; thinkingSignature = result.thinkingSignature; if (result.chunk) { yield result.chunk; } if (result.isDone) { // Поток завершен if (options?.onComplete) { options.onComplete(textContent); } return; } } } // Финальный чанк, если поток завершился без [DONE] if (options?.onComplete) { options.onComplete(textContent); } yield { text: '', isComplete: true, thinkingContent: thinkingContent.length > 0 ? thinkingContent : undefined, thinkingSignature: thinkingSignature.length > 0 ? thinkingSignature : undefined }; } catch (error) { const errorObj = (0, stream_utils_1.handleStreamError)(error); if (options?.onError) { options.onError(errorObj); } throw errorObj; } } /** * Отправляет запрос к модели в потоковом режиме (упрощенный интерфейс) * @param {string} prompt Запрос к модели * @param {ChatStreamOptions} [options] Дополнительные параметры * @returns {AsyncGenerator<ChatStreamChunk>} Асинхронный генератор чанков ответа */ async *streamPrompt(prompt, options) { const messages = [ { role: 'user', content: prompt } ]; yield* this.streamChat(messages, options); } /** * Создает новое WebSocket соединение для потокового чата * @returns {Promise<WebSocketConnectResponse>} Информация о созданном соединении */ async connectWebSocket() { return this.httpClient.post('/api/v1/chat/connect'); } /** * Отправляет сообщение в чат в потоковом режиме с поддержкой thinking через WebSocket * @param {ChatMessage[]} messages Массив сообщений для отправки * @param {ChatStreamOptions} [options] Дополнительные параметры * @param {EventHandler} [onEvent] Обработчик событий WebSocket * @returns {Promise<ThinkingStreamResponse>} Информация о потоковом запросе */ async streamChatWithThinking(messages, options = {}, onEvent) { if (!messages || messages.length === 0) { throw new Error('Необходимо предоставить хотя бы одно сообщение'); } try { // 1. Создаем параметры для запроса const socketId = `socket_${Date.now()}_${(0, websocket_helpers_1.generateId)(10)}`; // Создаем sessionId для отслеживания контекста между запросами const sessionId = options.sessionId || `thinking-${Date.now()}`; let socket = null; // 2. Подключаемся к WebSocket и ждем успешного подключения if (onEvent) { await new Promise((resolve, reject) => { try { const socketOptions = { transports: ['websocket'], reconnection: true, reconnectionAttempts: 3, reconnectionDelay: 1000, timeout: 10000, query: { socketId: socketId, token: options.authToken || 'test-token' } }; // Динамически импортируем socket.io-client Promise.resolve().then(() => __importStar(require('socket.io-client'))).then((socketIoModule) => { const io = socketIoModule.default || socketIoModule.io; if (!io) { reject(new Error('Не удалось импортировать socket.io-client')); return; } // Создаем соединение с сервером WebSocket const serverUrl = this.httpClient.getBaseURL?.() || 'http://localhost:3000'; socket = io(`${serverUrl}/reasoning`, socketOptions); // Устанавливаем обработчики событий (0, websocket_helpers_1.setupSocketEventHandlers)(socket, socketId, sessionId, onEvent); // Разрешаем промис, продолжаем выполнение resolve(); // Обработчик таймаута соединения setTimeout(() => { if (socket && !socket.connected) { reject(new Error('WebSocket connection timeout')); } }, 10000); }).catch(error => { const errorMessage = error instanceof Error ? error.message : String(error); reject(new Error(`Ошибка импорта socket.io-client: ${errorMessage}`)); }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); reject(new Error(`Ошибка WebSocket: ${errorMessage}`)); } }); } // 3. Отправляем HTTP запрос для инициации потока return this.httpClient.post('/api/v1/chat/stream-chat', { model: options.model || 'claude-3-7-sonnet-20240229', messages, temperature: options.temperature, maxTokens: options.maxTokens, socketId: socketId, sessionId: sessionId, // Добавляем sessionId для кэширования блоков мышления thinking: true, region: options.region }, { 'X-Socket-ID': socketId, 'X-Session-ID': sessionId // Добавляем ID сессии в заголовки }); } catch (error) { const errorObj = (0, stream_utils_1.handleStreamError)(error); if (options.onError) { options.onError(errorObj); } throw errorObj; } } } exports.ChatApi = ChatApi; //# sourceMappingURL=index.js.map