UNPKG

solver-sdk

Version:

SDK for WorkAI API - AI-powered code analysis with WorkCoins billing system

279 lines 13 kB
"use strict"; /** * 🔧 Delta-Chunking Utilities * Утилиты для обработки файлов, чанков и шифрования */ 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 __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.DeltaChunkingUtils = void 0; /** * Утилиты для Delta-Chunking операций */ class DeltaChunkingUtils { constructor(projects, options, environment, logger) { this.projects = projects; this.options = options; this.environment = environment; this.logger = logger; } /** * Валидация проекта через Projects API */ async validateProject(projectId) { try { // Используем существующий projects API для проверки await this.projects.getProject(projectId); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Проект ${projectId} не найден или недоступен: ${errorMessage}`); } } /** * Создание чанков из готовых файлов (полученных от клиента) * * ✅ ПРАВИЛЬНОЕ ИСПОЛЬЗОВАНИЕ: * Client Extension подготавливает массив FileContent и передает в Backend SDK * Backend SDK обрабатывает готовые данные и создает чанки для отправки на сервер */ async createChunksFromFiles(files) { const chunks = []; for (const file of files) { const fileChunks = await this.chunkFile(file); chunks.push(...fileChunks); } return chunks; } /** * Разбиение файла на чанки * * ✅ АРХИТЕКТУРА 2025: Используем размер в символах вместо неточной токенизации * * ПОЧЕМУ ИЗМЕНИЛИ: * - SDK не должен принимать решения о токенах - он не имеет точной информации * - На Backend есть точный подсчет токенов через Anthropic API * - Backend может делать перечанковку с точной токенизацией * - SDK создает чанки по символам, Backend оптимизирует по токенам */ async chunkFile(file) { // Простое разбиение на чанки по строкам (без tree-sitter пока) const lines = file.content.split('\n'); const chunks = []; const config = this.options.deltaChunking?.chunking || {}; // ✅ ИСПРАВЛЕНО: Убираем неточную токенизацию - используем размер в символах // Backend будет делать точную перечанковку с Anthropic API const maxChars = config.maxChars || 2000; // ~500 токенов при /3.8 const minChars = config.minChars || 500; // ~130 токенов при /3.8 let currentChunk = ''; let startLine = 1; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const newChunkContent = currentChunk + (currentChunk ? '\n' : '') + line; // Используем размер в символах вместо неточной токенизации const chunkSize = newChunkContent.length; if (chunkSize >= maxChars && currentChunk.length > 0) { // Создаем чанк если достигли лимита if (currentChunk.trim()) { chunks.push({ id: `${file.path}-${chunks.length}`, obfuscatedPath: this.obfuscatePath(file.path), content: currentChunk, metadata: { chunkType: this.detectChunkType(currentChunk), startLine, endLine: i, language: this.detectLanguage(file.path), originalSize: currentChunk.length, } }); } currentChunk = line; startLine = i + 1; } else { currentChunk = newChunkContent; } } // Добавляем последний чанк if (currentChunk.trim()) { chunks.push({ id: `${file.path}-${chunks.length}`, obfuscatedPath: this.obfuscatePath(file.path), content: currentChunk, metadata: { chunkType: this.detectChunkType(currentChunk), startLine, endLine: lines.length, language: this.detectLanguage(file.path), originalSize: currentChunk.length, } }); } return chunks; } /** * Шифрование чанков */ async encryptChunks(chunks) { if (!this.options.deltaChunking?.encryption?.enabled) { // Если шифрование отключено, просто конвертируем return chunks.map(chunk => ({ ...chunk, encryptedContent: Buffer.from(chunk.content, 'utf-8'), iv: Buffer.alloc(12, 0), // Пустой IV authTag: Buffer.alloc(16, 0), // Пустой authTag hash: this.calculateHash(chunk.content), })); } const encryptionKey = this.options.deltaChunking?.encryption?.key; if (!encryptionKey || encryptionKey.length !== 32) { throw new Error('Ключ шифрования должен содержать ровно 32 символа для AES-256'); } // Динамический импорт crypto для Node.js/браузера const crypto = this.environment === 'node' ? await Promise.resolve().then(() => __importStar(require('crypto'))) : null; // В браузере нужна другая реализация if (!crypto && this.environment === 'node') { throw new Error('Модуль crypto недоступен'); } return chunks.map(chunk => { if (this.environment === 'browser') { // 🌐 Web Crypto API для браузера (упрощенная реализация) this.logger.warn('⚠️ Используем упрощенное шифрование для браузера - НЕ для production!'); // Генерируем псевдо-случайные IV и AuthTag для совместимости const iv = new Uint8Array(12); const authTag = new Uint8Array(16); // Простая псевдо-случайная генерация на основе содержимого и времени const seed = chunk.content.length + Date.now(); for (let i = 0; i < 12; i++) { iv[i] = (seed + i * 17) % 256; } for (let i = 0; i < 16; i++) { authTag[i] = (seed + i * 23 + 42) % 256; } return { ...chunk, encryptedContent: Buffer.from(chunk.content, 'utf-8'), iv: Buffer.from(iv), authTag: Buffer.from(authTag), hash: this.calculateHash(chunk.content), }; } // AES-256-GCM шифрование для Node.js const iv = crypto.randomBytes(12); const cipher = crypto.createCipheriv('aes-256-gcm', Buffer.from(encryptionKey), iv); cipher.setAAD(Buffer.from(chunk.id, 'utf-8')); let encrypted = cipher.update(chunk.content, 'utf-8'); encrypted = Buffer.concat([encrypted, cipher.final()]); const authTag = cipher.getAuthTag(); return { ...chunk, encryptedContent: encrypted, iv, authTag, hash: this.calculateHash(chunk.content), }; }); } /** * Кросс-платформенная хеш-функция * * ✅ ПРАВИЛЬНОЕ ИСПОЛЬЗОВАНИЕ: * Для хеширования контента чанков и вспомогательных вычислений * НЕ для вычисления clientRootHash - это делает клиент! */ calculateHash(content) { // Используем улучшенный алгоритм djb2 для лучшего распределения хешей let hash = 5381; for (let i = 0; i < content.length; i++) { const char = content.charCodeAt(i); hash = ((hash << 5) + hash) + char; // hash * 33 + char } // Возвращаем положительный hex с фиксированной длиной return Math.abs(hash).toString(16).padStart(8, '0'); } /** * Обфускация пути файла */ obfuscatePath(filePath) { // Простая обфускация пути (в продакшене - более сложная) return Buffer.from(filePath).toString('base64').slice(0, 12); } /** * Определение типа чанка по содержимому */ detectChunkType(content) { if (content.includes('function ') || content.includes('async ') || content.includes('=>')) { return 'function'; } if (content.includes('class ') || content.includes('interface ') || content.includes('type ')) { return 'class'; } if (content.trim().startsWith('/*') || content.trim().startsWith('//')) { return 'comment'; } return 'block'; } /** * Определение языка программирования по расширению файла */ detectLanguage(filePath) { const ext = filePath.split('.').pop()?.toLowerCase(); const langMap = { 'ts': 'typescript', 'js': 'javascript', 'py': 'python', 'java': 'java', 'cpp': 'cpp', 'c': 'c', 'go': 'go', 'rs': 'rust', 'php': 'php', 'rb': 'ruby', }; return langMap[ext || ''] || 'plaintext'; } /** * Вычисляет хэш файла для отслеживания изменений * ✅ ИСПРАВЛЕНО: Унифицировано с основной hash функцией для кросс-платформенности */ calculateFileHash(content) { // Используем ту же hash функцию что и для calculateHash для консистентности // В будущем можно добавить crypto-based хеширование через динамический импорт return this.calculateHash(content); } } exports.DeltaChunkingUtils = DeltaChunkingUtils; //# sourceMappingURL=delta-chunking-utils.js.map