import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; export const defaults = Object.freeze({ llmModel: 'anthropic/claude-haiku-5.5', sttModel: 'scribe_v2_realtime', ttsModel: 'eleven_v4_turbo', voiceId: '21m00Tcm4TlvDq8ikWAM', systemPrompt: 'Ты голосовой помощник Алиса. Отвечай по-русски, коротко и по делу. Пиши так, чтобы ответ хорошо звучал вслух: обычный текст, без Markdown. Если не знаешь ответа, скажи об этом прямо.', }); export const ttsModels = ['eleven_v4_turbo', 'eleven_v3_conversational', 'eleven_flash_v2_5']; export function validateSettings(input) { if (typeof input !== 'object' || input === null) return null; const { llmModel, sttModel, ttsModel, voiceId, systemPrompt } = input; if (typeof llmModel !== 'string' || !/^[\w.~:-]+\/[\w.:~-]+$/.test(llmModel) || llmModel.length > 120) return null; if (sttModel !== defaults.sttModel || !ttsModels.includes(ttsModel)) return null; if (typeof voiceId !== 'string' || !/^[a-zA-Z0-9]{20}$/.test(voiceId)) return null; if (typeof systemPrompt !== 'string' || !systemPrompt.trim() || systemPrompt.length > 4000) return null; return { llmModel, sttModel, ttsModel, voiceId, systemPrompt: systemPrompt.trim() }; } export function openStore(directory = process.env.DATA_DIR ?? './data') { mkdirSync(directory, { recursive: true, mode: 0o700 }); const path = join(directory, 'assistant.json'); let state = { settings: { ...defaults }, history: [] }; try { const saved = JSON.parse(readFileSync(path, 'utf8')); const settings = validateSettings({ ...defaults, ...saved.settings }); if (!settings || !Array.isArray(saved.history)) throw new Error('Invalid assistant state'); state = { settings, history: saved.history }; } catch (error) { if (error.code !== 'ENOENT') throw error; } function commit(next) { const temporary = path + '.tmp'; writeFileSync(temporary, JSON.stringify(next) + '\n', { mode: 0o600 }); renameSync(temporary, path); state = next; } return { getSettings: () => ({ ...state.settings }), getHistory: () => state.history.map(turn => ({ ...turn })), updateSettings(settings) { commit({ ...state, settings }); }, recordTurn(user, assistant) { const turn = { user: user.slice(0, 6000), assistant: assistant.slice(0, 6000), createdAt: new Date().toISOString(), }; commit({ ...state, history: [...state.history.slice(-49), turn] }); }, clearHistory() { commit({ ...state, history: [] }); }, }; }