solver-sdk
Version:
SDK for WorkAI API - AI-powered code analysis with WorkCoins billing system
459 lines • 22.2 kB
JavaScript
;
/**
* Помощники для создания complex content сообщений согласно Anthropic API
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAssistantMessageWithThinking = createAssistantMessageWithThinking;
exports.createTextMessage = createTextMessage;
exports.createAssistantMessage = createAssistantMessage;
exports.extractThinkingBlocks = extractThinkingBlocks;
exports.extractToolUseBlocks = extractToolUseBlocks;
exports.extractToolResultBlocks = extractToolResultBlocks;
exports.extractTextContent = extractTextContent;
exports.createToolDefinition = createToolDefinition;
exports.createStandardDevelopmentTools = createStandardDevelopmentTools;
exports.createThinkingBlock = createThinkingBlock;
exports.createBatchToolResultMessage = createBatchToolResultMessage;
exports.createToolUseBlock = createToolUseBlock;
exports.createComplexAssistantMessage = createComplexAssistantMessage;
exports.ensureCorrectBlockOrder = ensureCorrectBlockOrder;
exports.validateAndFixAssistantMessage = validateAndFixAssistantMessage;
exports.createTextBlock = createTextBlock;
exports.createUserMessage = createUserMessage;
exports.createSystemMessage = createSystemMessage;
const thinking_block_manager_1 = require("./thinking-block-manager");
/**
* Создает сообщение assistant с thinking и tool_use блоками
* @param thinkingContent Содержимое мышления
* @param signature Криптографическая подпись
* @param toolCall Вызов инструмента
* @returns Сообщение assistant с complex content
*/
function createAssistantMessageWithThinking(thinkingContent, toolCall, signature // ✅ ЦЕНТРАЛИЗОВАННОЕ: signature опциональная (последний параметр)
) {
// 🏗️ ЦЕНТРАЛИЗОВАННОЕ создание thinking блока
const thinkingBlock = thinking_block_manager_1.ThinkingBlockManager.createThinkingBlock(thinkingContent, signature);
const content = [
thinkingBlock,
{
type: 'tool_use',
id: toolCall.id,
name: toolCall.name,
input: toolCall.input
}
];
return {
role: 'assistant',
content: content
};
}
/**
* Создает простое текстовое сообщение
* @param role Роль отправителя
* @param text Текст сообщения
* @returns Простое сообщение
*/
function createTextMessage(role, text) {
return {
role: role,
content: text
};
}
/**
* Создает сообщение assistant с текстом и опциональным мышлением
* @param text Основной текст ответа
* @param thinkingContent Содержимое мышления (опционально)
* @param signature Подпись мышления (опционально)
* @returns Сообщение assistant с complex content
*/
function createAssistantMessage(text, thinkingContent, signature) {
const content = [];
// Добавляем thinking блок если есть
if (thinkingContent) {
// 🏗️ ЦЕНТРАЛИЗОВАННОЕ создание thinking блока
const thinkingBlock = thinking_block_manager_1.ThinkingBlockManager.createThinkingBlock(thinkingContent, signature);
content.push(thinkingBlock);
}
// Добавляем текстовый блок
content.push({
type: 'text',
text: text
});
return {
role: 'assistant',
content: content
};
}
/**
* Извлекает thinking блоки из сообщения
* @param message Сообщение с complex content
* @returns Массив thinking блоков
*/
function extractThinkingBlocks(message) {
const content = Array.isArray(message) ? message :
(Array.isArray(message.content) ? message.content : []);
return content.filter(block => block.type === 'thinking' || block.type === 'redacted_thinking');
}
/**
* Извлекает tool_use блоки из сообщения
* @param message Сообщение с complex content
* @returns Массив tool_use блоков
*/
function extractToolUseBlocks(message) {
const content = Array.isArray(message) ? message :
(Array.isArray(message.content) ? message.content : []);
return content.filter(block => block.type === 'tool_use');
}
/**
* Извлекает tool_result блоки из сообщения
* @param message Сообщение с complex content
* @returns Массив tool_result блоков
*/
function extractToolResultBlocks(message) {
const content = Array.isArray(message) ? message :
(Array.isArray(message.content) ? message.content : []);
return content.filter(block => block.type === 'tool_result');
}
/**
* Извлекает текстовое содержимое из сообщения
* @param message Сообщение
* @returns Текстовое содержимое
*/
function extractTextContent(message) {
if (typeof message.content === 'string') {
return message.content;
}
const textBlocks = message.content.filter(block => block.type === 'text');
return textBlocks.map(block => block.text).join('');
}
/**
* Создает определение инструмента для Anthropic API
* @param name Название инструмента
* @param description Описание функциональности
* @param properties Свойства входных параметров
* @param required Обязательные поля
* @returns Определение инструмента
*/
function createToolDefinition(name, description, properties, required = []) {
return {
name: name,
description: description,
input_schema: {
type: 'object',
properties: properties,
required: required
}
};
}
/**
* Получает схемы инструментов с backend сервера
* @param backendUrl URL backend сервера (по умолчанию из SDK опций)
* @returns Массив инструментов от backend
*/
async function createStandardDevelopmentTools(backendUrl) {
const url = `${backendUrl || 'http://localhost:3000'}/api/v1/tools/schemas`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Backend недоступен: HTTP ${response.status} - ${response.statusText}`);
}
const data = await response.json();
if (!data.tools || !Array.isArray(data.tools)) {
throw new Error('Неверный формат ответа от backend');
}
console.log(`Загружено ${data.count} схем инструментов с backend`);
return data.tools;
}
/**
* Создает сообщение с контентом мышления
* @param thinking Текст мышления
* @param signature Криптографическая подпись (опциональная)
* @returns Блок контента с мышлением
*/
function createThinkingBlock(thinking, signature) {
// 🏗️ ЦЕНТРАЛИЗОВАННОЕ создание через ThinkingBlockManager
return thinking_block_manager_1.ThinkingBlockManager.createThinkingBlock(thinking, signature);
}
/**
* Создает user message с результатами выполнения инструментов
*
* ⚠️ КРИТИЧНО: Anthropic API требует, чтобы ВСЕ tool_result для одного assistant message
* были отправлены ВМЕСТЕ в одном user message!
*
* @example
* // Один результат:
* messages.push(createBatchToolResultMessage([
* { toolUseId: 'toolu_123', content: 'File content...' }
* ]));
*
* // Множество результатов (ПРАВИЛЬНО):
* const toolResults = await Promise.all(
* toolUses.map(async tool => ({
* toolUseId: tool.id,
* content: await executeTool(tool),
* }))
* );
* messages.push(createBatchToolResultMessage(toolResults));
*
* // С обработкой ошибок:
* messages.push(createBatchToolResultMessage([
* { toolUseId: 'toolu_123', content: 'Success result' },
* { toolUseId: 'toolu_456', content: 'Error: file not found', isError: true }
* ]));
*
* @param toolResults Массив результатов инструментов (минимум 1)
* @returns User message со всеми tool_result блоками
* @throws {Error} Если массив toolResults пустой
*/
function createBatchToolResultMessage(toolResults) {
if (toolResults.length === 0) {
throw new Error('createBatchToolResultMessage: toolResults не может быть пустым');
}
return {
role: 'user',
content: toolResults.map(result => ({
type: 'tool_result',
tool_use_id: result.toolUseId,
content: result.content,
is_error: result.isError || false
}))
};
}
/**
* Создает блок использования инструмента
* @param id ID вызова инструмента
* @param name Название инструмента
* @param input Параметры инструмента
* @returns Блок использования инструмента
*/
function createToolUseBlock(id, name, input) {
return {
type: 'tool_use',
id: id,
name: name,
input: input
};
}
/**
* Создает complex content сообщение, комбинируя разные блоки
* @param thinkingBlocks Блоки мышления
* @param toolUseBlocks Блоки использования инструментов
* @param textBlocks Текстовые блоки (опционально)
* @returns Сообщение ассистента с complex content
*/
function createComplexAssistantMessage(thinkingBlocks = [], toolUseBlocks = [], textBlocks = []) {
return {
role: 'assistant',
content: [...thinkingBlocks, ...toolUseBlocks, ...textBlocks]
};
}
/**
* ✅ КРИТИЧНО: Сортирует блоки в assistant message согласно требованиям Anthropic API
* При thinking mode: thinking блоки ВСЕГДА ПЕРВЫМИ!
*/
function ensureCorrectBlockOrder(content) {
if (!Array.isArray(content) || content.length === 0) {
return content;
}
// Разделяем блоки по типам
const thinkingBlocks = content.filter(b => b.type === 'thinking' || b.type === 'redacted_thinking');
const toolUseBlocks = content.filter(b => b.type === 'tool_use');
const textBlocks = content.filter(b => b.type === 'text');
const otherBlocks = content.filter(b => b.type !== 'thinking' &&
b.type !== 'redacted_thinking' &&
b.type !== 'tool_use' &&
b.type !== 'text');
// ✅ ПРАВИЛЬНЫЙ ПОРЯДОК согласно Anthropic API:
// 1. thinking ПЕРВЫМИ (ОБЯЗАТЕЛЬНО если есть tool_use)
// 2. tool_use
// 3. text
// 4. остальные
return [
...thinkingBlocks,
...toolUseBlocks,
...textBlocks,
...otherBlocks
];
}
/**
* ✅ Валидирует структуру assistant message перед отправкой
* 🚨 ERROR FIRST: Только валидация, БЕЗ автоисправлений!
* Кидает exception с детальной диагностикой и инструкциями по исправлению
*/
function validateAndFixAssistantMessage(message) {
// Проверяем что это assistant message
if (message.role !== 'assistant') {
return message;
}
// Если content - строка, оставляем как есть
if (typeof message.content === 'string') {
return message;
}
// Если content - массив, валидируем структуру
if (Array.isArray(message.content)) {
const blockTypes = message.content.map(b => b.type);
const structure = blockTypes.join(' → ');
const hasToolUse = message.content.some(b => b.type === 'tool_use');
const hasThinking = message.content.some(b => b.type === 'thinking' || b.type === 'redacted_thinking');
const hasText = message.content.some(b => b.type === 'text');
// ❌ КРИТИЧЕСКАЯ ОШИБКА #1: tool_use без thinking
if (hasToolUse && !hasThinking) {
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.error('❌ [SDK VALIDATION ERROR] Anthropic API отклонит этот запрос!');
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.error('');
console.error('📋 ПРОБЛЕМА:');
console.error(' Assistant message содержит tool_use БЕЗ thinking блока');
console.error('');
console.error('📊 ТЕКУЩАЯ СТРУКТУРА:');
console.error(` ${structure}`);
console.error('');
console.error('✅ ТРЕБУЕМАЯ СТРУКТУРА:');
console.error(' thinking → tool_use → [text]');
console.error('');
console.error('🔍 ДЕТАЛИ:');
console.error(` - Блоков thinking: ${hasThinking ? 'есть' : '❌ НЕТ'}`);
console.error(` - Блоков tool_use: ${message.content.filter(b => b.type === 'tool_use').length}`);
console.error(` - Блоков text: ${message.content.filter(b => b.type === 'text').length}`);
console.error('');
console.error('🔧 КАК ИСПРАВИТЬ В ВАШЕМ КОДЕ:');
console.error('');
console.error(' 1️⃣ Убедитесь что вы накапливаете thinking из SSE:');
console.error('');
console.error(' let thinkingBlock = { type: "thinking", thinking: "" };');
console.error(' ');
console.error(' stream.on("content_block_start", (event) => {');
console.error(' if (event.content_block.type === "thinking") {');
console.error(' thinkingBlock.thinking = "";');
console.error(' }');
console.error(' });');
console.error('');
console.error(' stream.on("content_block_delta", (event) => {');
console.error(' if (event.delta.type === "thinking_delta") {');
console.error(' thinkingBlock.thinking += event.delta.thinking;');
console.error(' }');
console.error(' });');
console.error('');
console.error(' 2️⃣ При формировании assistant message добавьте thinking ПЕРВЫМ:');
console.error('');
console.error(' const message = {');
console.error(' role: "assistant",');
console.error(' content: [');
console.error(' thinkingBlock, // ← thinking ПЕРВЫМ!');
console.error(' toolUseBlock');
console.error(' ]');
console.error(' };');
console.error('');
console.error('📚 Документация Anthropic:');
console.error(' https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking');
console.error('');
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
throw new Error(`[SDK VALIDATION] Assistant message с tool_use ОБЯЗАН содержать thinking блок! Структура: ${structure}`);
}
// ❌ КРИТИЧЕСКАЯ ОШИБКА #2: Неправильный порядок блоков
const firstBlock = message.content[0];
if (hasThinking && firstBlock.type !== 'thinking' && firstBlock.type !== 'redacted_thinking') {
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.error('❌ [SDK VALIDATION ERROR] Anthropic API отклонит этот запрос!');
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.error('');
console.error('📋 ПРОБЛЕМА:');
console.error(' Неправильный порядок блоков в assistant message');
console.error('');
console.error('📊 ТЕКУЩАЯ СТРУКТУРА:');
console.error(` ${structure}`);
console.error('');
console.error('❌ ПЕРВЫЙ БЛОК:');
console.error(` ${firstBlock.type} (НЕПРАВИЛЬНО!)`);
console.error('');
console.error('✅ ОЖИДАЕТСЯ:');
console.error(' thinking (должен быть ПЕРВЫМ!)');
console.error('');
console.error('🔍 ДЕТАЛИ БЛОКОВ:');
message.content.forEach((block, idx) => {
const emoji = idx === 0 ? (block.type === 'thinking' ? '✅' : '❌') : ' ';
console.error(` ${emoji} [${idx}] ${block.type}`);
if (block.type === 'thinking') {
const preview = block.thinking?.substring(0, 50) || '';
const thinkingLength = block.thinking?.length || 0;
console.error(` └─ "${preview}${thinkingLength > 50 ? '...' : ''}"`);
}
if (block.type === 'tool_use') {
console.error(` └─ tool: ${block.name}, id: ${block.id?.substring(0, 12)}...`);
}
});
console.error('');
console.error('🔧 КАК ИСПРАВИТЬ В ВАШЕМ КОДЕ:');
console.error('');
console.error(' ❌ НЕ ДЕЛАЙТЕ ТАК:');
console.error(' const content = [textBlock, thinkingBlock, toolUseBlock]; // НЕПРАВИЛЬНО!');
console.error('');
console.error(' ✅ ДЕЛАЙТЕ ТАК:');
console.error(' const content = [');
console.error(' ...thinkingBlocks, // thinking ВСЕГДА первыми!');
console.error(' ...toolUseBlocks,');
console.error(' ...textBlocks');
console.error(' ];');
console.error('');
console.error(' Или используйте helper функцию:');
console.error(' import { ensureCorrectBlockOrder } from "solver-sdk";');
console.error(' const sortedContent = ensureCorrectBlockOrder(blocks);');
console.error('');
console.error('📚 Документация Anthropic:');
console.error(' https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking');
console.error('');
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
throw new Error(`[SDK VALIDATION] Assistant message должен начинаться с thinking блока! Первый блок: ${firstBlock.type}, структура: ${structure}`);
}
// ✅ ФИКС: Очищаем невалидные signature из thinking блоков перед отправкой на бэкенд
// Anthropic API требует либо валидную signature, либо вообще не иметь этого поля
const cleanedContent = message.content.map((block) => {
if ((block.type === 'thinking' || block.type === 'redacted_thinking') && block.hasOwnProperty('signature')) {
const hasValidSignature = block.signature &&
typeof block.signature === 'string' &&
block.signature.length > 0;
if (!hasValidSignature) {
// Удаляем невалидную signature (пустая строка или undefined)
const { signature, ...blockWithoutSignature } = block;
return blockWithoutSignature;
}
}
return block;
});
return { ...message, content: cleanedContent };
}
return message;
}
/**
* Конвертирует текст в ContentBlock
* @param text Текст сообщения
* @returns Блок текстового контента
*/
function createTextBlock(text) {
return {
type: 'text',
text: text
};
}
/**
* Создает сообщение пользователя с текстом
* @param text Текст сообщения
* @returns Сообщение пользователя
*/
function createUserMessage(text) {
return {
role: 'user',
content: text
};
}
/**
* Создает системное сообщение
* @param text Текст системного сообщения
* @returns Системное сообщение
*/
function createSystemMessage(text) {
return {
role: 'system',
content: text
};
}
//# sourceMappingURL=message-helpers.js.map