152 lines
4.6 KiB
JavaScript
152 lines
4.6 KiB
JavaScript
'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,
|
|
};
|