solver-sdk
Version:
SDK for WorkAI API - AI-powered code analysis with WorkCoins billing system
279 lines • 13 kB
JavaScript
;
/**
* 🔧 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