import * as vscode from 'vscode'; import * as path from 'path'; import { ChatMessage, TaskMeta, TaskSession, ConversationMeta, MessageType, UserMessage, AiMessage, SystemMessage } from '../types/chatHistory'; /** * 会话历史管理器 * 按照设计文档实现:~/.iccoder/projects/{项目路径编码}/{taskId}/ */ export class ChatHistoryManager { private static instance: ChatHistoryManager; private baseDir: string; // ~/.iccoder private currentTaskId: string | null = null; private currentProjectPath: string | null = null; private constructor() { // 设置存储路径: ~/.iccoder const userHome = process.env.USERPROFILE || process.env.HOME || ''; this.baseDir = path.join(userHome, '.iccoder'); this.ensureBaseDir(); } /** * 项目路径编码 * 规则: * - 替换 \ 和 / 为 -- * - 替换 : 为空 * 例如:C:\Users\admin\Documents\Project -> C--Users-admin-Documents-Project */ private encodeProjectPath(projectPath: string): string { return projectPath .replace(/:/g, '') // 移除冒号 .replace(/[/\\]/g, '--'); // 替换斜杠为 -- } /** * 生成任务ID * 格式:task_{date}_{sequence} */ private generateTaskId(): string { const date = new Date().toISOString().split('T')[0].replace(/-/g, ''); const sequence = Math.random().toString(36).substr(2, 6); return `task_${date}_${sequence}`; } /** * 获取任务目录路径 */ private getTaskDir(projectPath: string, taskId: string): string { const encodedPath = this.encodeProjectPath(projectPath); return path.join(this.baseDir, 'projects', encodedPath, taskId); } /** * 确保基础目录存在 */ private async ensureBaseDir(): Promise { try { const uri = vscode.Uri.file(this.baseDir); try { await vscode.workspace.fs.stat(uri); } catch { // 目录不存在,创建它 await vscode.workspace.fs.createDirectory(uri); console.log(`创建存储目录: ${this.baseDir}`); } } catch (error) { console.error("创建存储目录失败:", error); vscode.window.showErrorMessage("创建会话历史存储目录失败"); } } /** * 确保任务目录存在 */ private async ensureTaskDir(taskDir: string): Promise { try { const uri = vscode.Uri.file(taskDir); try { await vscode.workspace.fs.stat(uri); } catch { // 目录不存在,创建它 await vscode.workspace.fs.createDirectory(uri); console.log(`创建任务目录: ${taskDir}`); } } catch (error) { console.error("创建任务目录失败:", error); throw error; } } /** * 获取单例实例 */ public static getInstance(): ChatHistoryManager { if (!ChatHistoryManager.instance) { ChatHistoryManager.instance = new ChatHistoryManager(); } return ChatHistoryManager.instance; } /** * 创建新任务 */ public async createTask(projectPath: string, taskName: string): Promise { const taskId = this.generateTaskId(); const now = new Date().toISOString(); const meta: TaskMeta = { taskId, taskName, projectPath, createdAt: now, updatedAt: now, stats: { credits: 0, totalTokens: 0, inputTokens: 0, outputTokens: 0 } }; this.currentTaskId = taskId; this.currentProjectPath = projectPath; // 创建任务目录 const taskDir = this.getTaskDir(projectPath, taskId); await this.ensureTaskDir(taskDir); // 保存 meta.json await this.saveTaskMeta(meta); // 初始化空的 conversation.json await this.saveConversation([]); return meta; } /** * 保存任务元数据 */ private async saveTaskMeta(meta: TaskMeta): Promise { if (!this.currentTaskId || !this.currentProjectPath) { throw new Error("没有当前任务"); } const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId); const metaPath = path.join(taskDir, 'meta.json'); try { const uri = vscode.Uri.file(metaPath); const content = Buffer.from(JSON.stringify(meta, null, 2), 'utf-8'); await vscode.workspace.fs.writeFile(uri, content); } catch (error) { console.error("保存任务元数据失败:", error); throw error; } } /** * 加载任务元数据 */ private async loadTaskMeta(): Promise { if (!this.currentTaskId || !this.currentProjectPath) { return null; } const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId); const metaPath = path.join(taskDir, 'meta.json'); try { const uri = vscode.Uri.file(metaPath); const content = await vscode.workspace.fs.readFile(uri); const data = Buffer.from(content).toString('utf-8'); return JSON.parse(data); } catch (error) { // 文件不存在或读取失败 return null; } } /** * 保存对话历史(conversation.json) */ private async saveConversation(messages: ChatMessage[]): Promise { if (!this.currentTaskId || !this.currentProjectPath) { throw new Error("没有当前任务"); } const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId); const conversationPath = path.join(taskDir, 'conversation.json'); try { const uri = vscode.Uri.file(conversationPath); const content = Buffer.from(JSON.stringify(messages, null, 2), 'utf-8'); await vscode.workspace.fs.writeFile(uri, content); } catch (error) { console.error("保存对话历史失败:", error); throw error; } } /** * 加载对话历史 */ private async loadConversation(): Promise { if (!this.currentTaskId || !this.currentProjectPath) { return []; } const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId); const conversationPath = path.join(taskDir, 'conversation.json'); try { const uri = vscode.Uri.file(conversationPath); const content = await vscode.workspace.fs.readFile(uri); const data = Buffer.from(content).toString('utf-8'); return JSON.parse(data); } catch (error) { // 文件不存在或读取失败 return []; } } /** * 追加对话元数据(conversation_meta.jsonl) */ private async appendConversationMeta(meta: ConversationMeta): Promise { if (!this.currentTaskId || !this.currentProjectPath) { throw new Error("没有当前任务"); } const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId); const metaPath = path.join(taskDir, 'conversation_meta.jsonl'); try { const uri = vscode.Uri.file(metaPath); const line = JSON.stringify(meta) + '\n'; // 读取现有内容 let existingContent = ''; try { const content = await vscode.workspace.fs.readFile(uri); existingContent = Buffer.from(content).toString('utf-8'); } catch { // 文件不存在,忽略错误 } // 追加新内容 const newContent = existingContent + line; await vscode.workspace.fs.writeFile(uri, Buffer.from(newContent, 'utf-8')); } catch (error) { console.error("追加对话元数据失败:", error); throw error; } } /** * 确保有当前任务,如果没有则自动创建 */ private async ensureCurrentTask(): Promise { if (!this.currentTaskId || !this.currentProjectPath) { // 获取当前工作区路径 const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; if (workspacePath) { await this.createTask(workspacePath, "默认任务"); } else { throw new Error("没有打开的工作区,无法创建任务"); } } } /** * 添加用户消息 */ public async addUserMessage(text: string): Promise { await this.ensureCurrentTask(); const messages = await this.loadConversation(); const userMessage: UserMessage = { type: MessageType.USER, contents: [{ type: "TEXT", text }] }; messages.push(userMessage); await this.saveConversation(messages); // 更新任务元数据 await this.updateTaskTimestamp(); } /** * 添加AI消息 */ public async addAiMessage(text: string, toolRequests?: any[]): Promise { await this.ensureCurrentTask(); const messages = await this.loadConversation(); const aiMessage: AiMessage = { type: MessageType.AI, text, toolExecutionRequests: toolRequests }; messages.push(aiMessage); await this.saveConversation(messages); // 更新任务元数据 await this.updateTaskTimestamp(); } /** * 添加系统消息 */ public async addSystemMessage(text: string): Promise { await this.ensureCurrentTask(); const messages = await this.loadConversation(); const systemMessage: SystemMessage = { type: MessageType.SYSTEM, text }; messages.push(systemMessage); await this.saveConversation(messages); } /** * 记录对话轮次元数据 */ public async recordTurnMeta( turnId: number, usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number }, model?: string, duration?: number ): Promise { const meta: ConversationMeta = { turnId, timestamp: new Date().toISOString(), usage, model, duration }; await this.appendConversationMeta(meta); // 更新任务统计 if (usage) { await this.updateTaskStats(usage); } } /** * 更新任务时间戳 */ private async updateTaskTimestamp(): Promise { const meta = await this.loadTaskMeta(); if (meta) { meta.updatedAt = new Date().toISOString(); await this.saveTaskMeta(meta); } } /** * 更新任务统计 */ private async updateTaskStats(usage: { inputTokens?: number; outputTokens?: number; totalTokens?: number }): Promise { const meta = await this.loadTaskMeta(); if (meta) { meta.stats.inputTokens += usage.inputTokens || 0; meta.stats.outputTokens += usage.outputTokens || 0; meta.stats.totalTokens += usage.totalTokens || 0; meta.updatedAt = new Date().toISOString(); await this.saveTaskMeta(meta); } } /** * 获取当前任务会话 */ public async getCurrentTaskSession(): Promise { const meta = await this.loadTaskMeta(); if (!meta) { return null; } const messages = await this.loadConversation(); const conversationMeta = await this.loadConversationMeta(); return { meta, messages, conversationMeta }; } /** * 加载对话元数据 */ private async loadConversationMeta(): Promise { if (!this.currentTaskId || !this.currentProjectPath) { return []; } const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId); const metaPath = path.join(taskDir, 'conversation_meta.jsonl'); try { const uri = vscode.Uri.file(metaPath); const content = await vscode.workspace.fs.readFile(uri); const data = Buffer.from(content).toString('utf-8'); return data .split('\n') .filter(line => line.trim()) .map(line => JSON.parse(line)); } catch (error) { // 文件不存在或读取失败 return []; } } /** * 列出项目的所有任务 */ public async listProjectTasks(projectPath: string): Promise { const encodedPath = this.encodeProjectPath(projectPath); const projectDir = path.join(this.baseDir, 'projects', encodedPath); try { const uri = vscode.Uri.file(projectDir); const entries = await vscode.workspace.fs.readDirectory(uri); const tasks: TaskMeta[] = []; for (const [taskId, type] of entries) { if (type === vscode.FileType.Directory) { const metaPath = path.join(projectDir, taskId, 'meta.json'); try { const metaUri = vscode.Uri.file(metaPath); const content = await vscode.workspace.fs.readFile(metaUri); const data = Buffer.from(content).toString('utf-8'); tasks.push(JSON.parse(data)); } catch (error) { console.error(`加载任务 ${taskId} 失败:`, error); } } } return tasks.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() ); } catch (error) { // 目录不存在 return []; } } /** * 切换到指定任务 */ public async switchTask(projectPath: string, taskId: string): Promise { const taskDir = this.getTaskDir(projectPath, taskId); const metaPath = path.join(taskDir, 'meta.json'); try { const uri = vscode.Uri.file(metaPath); await vscode.workspace.fs.stat(uri); this.currentProjectPath = projectPath; this.currentTaskId = taskId; return true; } catch { return false; } } /** * 获取当前任务ID */ public getCurrentTaskId(): string | null { return this.currentTaskId; } /** * 获取基础目录 */ public getBaseDir(): string { return this.baseDir; } }