solver-sdk
Version:
SDK для интеграции с Code Solver Backend API
388 lines • 19.6 kB
JavaScript
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
;