Заменить ClaudeStreamProcessor (spawn claude) на ClaudeSDKProcessor (SDK) с нулевыми изменениями в функционале.
- ✅ Все текущие функции работают точно так же
- ✅ Никаких новых фич (Telegram tools и т.д.)
- ✅ Только bot1 переходит на SDK
- ✅ Bot2-4 остаются на spawn подходе
SessionManager → ClaudeStreamProcessor → spawn('claude', args) → STDIO → Stream parsingSessionManager → ClaudeSDKProcessor → SDK.query(prompt, options) → Event streamВсе остальное остается ТОЧНО ТАК ЖЕ!
// claude-stream-processor.js - что нужно заменить
class ClaudeStreamProcessor {
async startNewConversation(prompt) // → SDK.query(prompt)
async continueConversation(prompt) // → SDK.query(prompt, {continue: true})
async resumeSession(sessionId, prompt) // → SDK.query(prompt, {resume: sessionId})
// События, которые должны остаться:
this.emit('data', message) // Поток сообщений
this.emit('session-id', id) // ID сессии
this.emit('end', {exitCode}) // Завершение
this.emit('error', error) // Ошибки
this.emit('prompt-too-long') // Авто-компакт
}// Текущие аргументы claude
['-p', '--model', model, '--output-format', 'stream-json', '--verbose', '--dangerously-skip-permissions', prompt]
['-c', '-p', ...] // continue
['-r', sessionId, '-p', ...] // resume
// SDK эквиваленты
{
model: model,
outputFormat: 'stream-json',
verbose: true,
skipPermissions: true
}npm install @anthropic-ai/claude-code// ClaudeSDKProcessor.js
const { query } = require('@anthropic-ai/claude-code');
const { EventEmitter } = require('events');
class ClaudeSDKProcessor extends EventEmitter {
constructor(options = {}) {
super();
this.options = {
model: 'sonnet',
workingDirectory: process.cwd(),
verbose: true,
skipPermissions: true,
...options
};
this.currentQuery = null;
this.sessionId = null;
this.isProcessing = false;
}
async startNewConversation(prompt) {
if (this.isProcessing) {
throw new Error('Already processing a request');
}
this.isProcessing = true;
const options = {
model: this.options.model,
workingDirectory: this.options.workingDirectory,
outputFormat: 'stream-json',
verbose: this.options.verbose,
skipPermissions: this.options.skipPermissions
};
try {
this.currentQuery = query(prompt, options);
for await (const message of this.currentQuery) {
// Эмулируем события ClaudeStreamProcessor
this.emit('data', message);
// Сохраняем session ID
if (message.type === 'session_id') {
this.sessionId = message.session_id;
this.emit('session-id', this.sessionId);
}
}
this.emit('end', { exitCode: 0 });
} catch (error) {
this.emit('error', error);
} finally {
this.isProcessing = false;
this.currentQuery = null;
}
}
async continueConversation(prompt, sessionId = null) {
if (this.isProcessing) {
throw new Error('Already processing a request');
}
this.isProcessing = true;
const options = {
model: this.options.model,
workingDirectory: this.options.workingDirectory,
outputFormat: 'stream-json',
verbose: this.options.verbose,
skipPermissions: this.options.skipPermissions,
continueSession: true // SDK equivalent of -c flag
};
try {
this.currentQuery = query(prompt, options);
for await (const message of this.currentQuery) {
this.emit('data', message);
}
this.emit('end', { exitCode: 0 });
} catch (error) {
this.emit('error', error);
} finally {
this.isProcessing = false;
this.currentQuery = null;
}
}
async resumeSession(sessionId, prompt) {
if (this.isProcessing) {
throw new Error('Already processing a request');
}
this.isProcessing = true;
const options = {
model: this.options.model,
workingDirectory: this.options.workingDirectory,
outputFormat: 'stream-json',
verbose: this.options.verbose,
skipPermissions: this.options.skipPermissions,
resumeSession: sessionId // SDK equivalent of -r sessionId
};
try {
this.currentQuery = query(prompt, options);
for await (const message of this.currentQuery) {
this.emit('data', message);
}
this.emit('end', { exitCode: 0 });
} catch (error) {
this.emit('error', error);
} finally {
this.isProcessing = false;
this.currentQuery = null;
}
}
cancel() {
if (this.currentQuery && this.currentQuery.cancel) {
this.currentQuery.cancel();
this.currentQuery = null;
this.isProcessing = false;
}
}
// Методы совместимости с текущим API
getLastClaudeArgs() {
// Для тестов - возвращаем эквивалент аргументов
return ['-p', '--model', this.options.model, '--output-format', 'stream-json'];
}
getLastClaudeOptions() {
return {
cwd: this.options.workingDirectory,
stdio: ['ignore', 'pipe', 'pipe']
};
}
}
module.exports = ClaudeSDKProcessor;// ConfigManager.js - добавить метод
getClaudeSDKEnabled() {
const config = this.getConfig();
return config.useClaudeSDK === true;
}// SessionManager.js - изменить метод createUserSession
async createUserSession(userId, chatId) {
const userModel = this.getUserModel(userId) || this.options.model;
// Определяем тип процессора через feature flag
const useSDK = this.mainBot?.configManager?.getClaudeSDKEnabled() || false;
let processor;
if (useSDK) {
console.log(`[SessionManager] Using Claude SDK for user ${userId}`);
const ClaudeSDKProcessor = require('./ClaudeSDKProcessor');
processor = new ClaudeSDKProcessor({
model: userModel,
workingDirectory: this.options.workingDirectory
});
} else {
console.log(`[SessionManager] Using Claude Stream for user ${userId}`);
const ClaudeStreamProcessor = require('./claude-stream-processor');
processor = new ClaudeStreamProcessor({
model: userModel,
workingDirectory: this.options.workingDirectory
});
}
// Весь остальной код остается БЕЗ ИЗМЕНЕНИЙ!
// setupProcessorEvents, session creation, etc.
this.setupProcessorEvents(processor, session);
this.userSessions.set(userId, session);
this.activeProcessors.add(processor);
return session;
}// configs/bot1.json
{
"useClaudeSDK": true,
"adminUserId": "...",
"botToken": "..."
}// configs/bot2.json, bot3.json, bot4.json
// НЕ ДОБАВЛЯЕМ useClaudeSDK - defaults to false// tests/unit/claude-sdk-processor.test.js
describe('ClaudeSDKProcessor', () => {
test('should have same interface as ClaudeStreamProcessor', () => {
const processor = new ClaudeSDKProcessor();
// Проверяем что все методы на месте
expect(processor.startNewConversation).toBeDefined();
expect(processor.continueConversation).toBeDefined();
expect(processor.resumeSession).toBeDefined();
expect(processor.cancel).toBeDefined();
});
test('should emit same events as stream processor', async () => {
// Mock тест на события
});
});# Тест bot1 (SDK)
NODE_ENV=test npm test -- --testNamePattern="bot1.*SDK"
# Тест bot2 (Stream - regression)
NODE_ENV=test npm test -- --testNamePattern="bot2.*Stream"- Telegram custom tools (send_image, send_document, etc.)
- MCP servers
- canUseTool callbacks
- Новые возможности SDK
- Дополнительные фичи
- Замена spawn('claude') → SDK.query()
- Сохранение точно тех же событий
- Feature flag для безопасности
- Совместимость API
| Риск | Процессный подход | SDK подход |
|---|---|---|
| Производительность | spawn() overhead | ✅ Нативный SDK |
| Надежность | Process crashes | ✅ In-process |
| Debugging | STDIO parsing | ✅ Прямые события |
| Maintenance | CLI аргументы | ✅ Typed options |
- Phase 1: ClaudeSDKProcessor - 2-3 часа
- Phase 2: SessionManager integration - 1-2 часа
- Phase 3: Configuration - 10 минут
- Phase 4: Testing - 1 час
Общее время: 4-6 часов
- Bot1: Все команды работают точно так же (status, new_session, etc.)
- Bot1: Сессии создаются и продолжаются без различий
- Bot1: Voice сообщения обрабатываются так же
- Bot1: File uploads работают так же
- Bot1: Git operations работают так же
- Bot2-4: Никаких изменений в поведении
- Все события (data, session-id, end, error) работают идентично
- SessionManager API остается неизменным
- Все unit тесты проходят
- Performance не хуже (скорее лучше)
- Feature flag позволяет мгновенный откат
- Bot tokens и sensitive data не меняют обработку
- Логи показывают четко: SDK vs Stream
- Deploy на dev с bot1 feature flag
- Тестирование всех основных сценариев
- Production bot1 с feature flag ON
- Monitoring 24-48 часов
- Rollback или expand в зависимости от результатов
// Одна строчка для отката
{ "useClaudeSDK": false }Максимально простая и безопасная миграция:
- ✅ 0 новых фич - только замена архитектуры
- ✅ 0 изменений API - все методы остаются те же
- ✅ 0 риска для bot2-4 - они остаются на spawn
- ✅ 1 feature flag для контроля
- ✅ 4-6 часов реализации
Готов начинать реализацию когда скажете! 🚀