'use strict'; const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const { toISO } = require('../utils/time'); const { normalizeTokenUsage } = require('../utils/token_usage'); function getWorkspaceStore(workspace) { const base = path.join(workspace, '.easyagent'); const convDir = path.join(base, 'conversations'); const indexFile = path.join(base, 'index.json'); if (!fs.existsSync(convDir)) fs.mkdirSync(convDir, { recursive: true }); if (!fs.existsSync(indexFile)) fs.writeFileSync(indexFile, JSON.stringify({}, null, 2), 'utf8'); return { base, convDir, indexFile }; } function newConversationId() { return crypto.randomUUID(); } function loadIndex(indexFile) { try { const raw = fs.readFileSync(indexFile, 'utf8'); return raw ? JSON.parse(raw) : {}; } catch (_) { return {}; } } function saveIndex(indexFile, index) { fs.writeFileSync(indexFile, JSON.stringify(index, null, 2), 'utf8'); } function extractTitle(messages) { for (const msg of messages) { if (msg.role === 'user') { let content = ''; if (typeof msg.content === 'string') { content = msg.content.trim(); } else if (Array.isArray(msg.content)) { content = msg.content .filter((part) => part && part.type === 'text' && typeof part.text === 'string') .map((part) => part.text) .join(' ') .trim(); } if (content) return content.length > 50 ? `${content.slice(0, 50)}...` : content; } } return '新对话'; } function countTools(messages) { let count = 0; for (const msg of messages) { if (msg.role === 'assistant' && Array.isArray(msg.tool_calls)) count += msg.tool_calls.length; if (msg.role === 'tool') count += 1; } return count; } function saveConversation(workspace, conversation) { const { convDir, indexFile } = getWorkspaceStore(workspace); const filePath = path.join(convDir, `${conversation.id}.json`); fs.writeFileSync(filePath, JSON.stringify(conversation, null, 2), 'utf8'); const index = loadIndex(indexFile); index[conversation.id] = { title: conversation.title || '新对话', created_at: conversation.created_at, updated_at: conversation.updated_at, total_messages: (conversation.messages || []).length, total_tools: countTools(conversation.messages || []), thinking_mode: conversation.metadata?.thinking_mode || false, run_mode: conversation.metadata?.thinking_mode ? 'thinking' : 'fast', model_key: conversation.metadata?.model_key || '', }; saveIndex(indexFile, index); } function createConversation(workspace, metadata = {}) { const id = newConversationId(); const now = toISO(); const conversation = { id, title: '新对话', created_at: now, updated_at: now, metadata: { model_key: metadata.model_key || '', model_id: metadata.model_id || '', thinking_mode: !!metadata.thinking_mode, allow_mode: metadata.allow_mode || 'full_access', token_usage: normalizeTokenUsage(metadata.token_usage), cwd: metadata.cwd || '', }, messages: [], }; saveConversation(workspace, conversation); return conversation; } function loadConversation(workspace, id) { const { convDir } = getWorkspaceStore(workspace); const filePath = path.join(convDir, `${id}.json`); if (!fs.existsSync(filePath)) return null; const raw = fs.readFileSync(filePath, 'utf8'); if (!raw) return null; return JSON.parse(raw); } function listConversations(workspace) { const { indexFile } = getWorkspaceStore(workspace); const index = loadIndex(indexFile); const entries = Object.entries(index).map(([id, meta]) => ({ id, ...meta })); entries.sort((a, b) => { const ta = new Date(a.updated_at || a.created_at || 0).getTime(); const tb = new Date(b.updated_at || b.created_at || 0).getTime(); return tb - ta; }); return entries; } function updateConversation(workspace, conversation, messages, metadataUpdates = {}) { const now = toISO(); const updated = { ...conversation, messages, updated_at: now, }; updated.title = extractTitle(messages); updated.metadata = { ...conversation.metadata, ...metadataUpdates, token_usage: metadataUpdates && Object.prototype.hasOwnProperty.call(metadataUpdates, 'token_usage') ? normalizeTokenUsage(metadataUpdates.token_usage) : normalizeTokenUsage(conversation.metadata?.token_usage), }; saveConversation(workspace, updated); return updated; } module.exports = { getWorkspaceStore, createConversation, loadConversation, listConversations, updateConversation, saveConversation, };