/** * 对话服务 * 整合 SSE 通信、工具执行、用户交互 */ import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import { startStreamDialog, generateTaskId, SSEController, SSECallbacks } from './sseHandler'; import { executeToolCall, createToolExecutorContext, ToolExecutorContext } from './toolExecutor'; import { userInteractionManager } from './userInteraction'; import { getConfig } from '../config/settings'; import type { DialogRequest, ToolCallRequest, AskUserEvent, RunMode, ToolConfirmEvent, PlanConfirmEvent } from '../types/api'; import { submitToolConfirm, submitAnswer } from './apiClient'; import { ChatHistoryManager } from '../utils/chatHistoryManager'; /** * 消息段落类型 */ export interface MessageSegment { type: 'text' | 'tool' | 'question' | 'agent' | 'plan'; content?: string; toolName?: string; toolStatus?: 'running' | 'success' | 'error'; toolResult?: string; askId?: string; question?: string; options?: string[]; // 智能体相关字段 agentId?: string; agentName?: string; agentStatus?: 'running' | 'completed' | 'error'; agentSteps?: AgentStep[]; // 计划相关字段 planTitle?: string; planSteps?: string[]; planSummary?: string; } /** * 智能体执行步骤 */ export interface AgentStep { step: number; toolName: string; toolInput?: unknown; toolResult?: string; status: 'running' | 'completed' | 'error'; } /** * 对话回调接口 */ export interface DialogCallbacks { /** 收到文本(可能多次调用,流式) */ onText?: (text: string, isStreaming: boolean) => void; /** 工具开始执行 */ onToolStart?: (toolName: string) => void; /** 工具执行完成 */ onToolComplete?: (toolName: string, result: string) => void; /** 工具执行错误 */ onToolError?: (toolName: string, error: string) => void; /** 工具确认请求(Ask 模式) */ onToolConfirm?: (confirmId: number, toolName: string, toolInput: Record) => void; /** 计划确认请求(Plan 模式) */ onPlanConfirm?: (confirmId: number, title: string, steps: string[], summary: string) => void; /** 显示问题(ask_user) */ onQuestion?: (askId: string, question: string, options: string[]) => void; /** 实时更新段落(流式过程中) */ onSegmentUpdate?: (segments: MessageSegment[]) => void; /** 对话完成,返回所有段落 */ onComplete?: (segments: MessageSegment[]) => void; /** 错误 */ onError?: (message: string) => void; /** 通知消息 */ onNotification?: (message: string) => void; } /** * 对话会话 */ export class DialogSession { private taskId: string; private sseController: SSEController | null = null; private toolContext: ToolExecutorContext; private accumulatedText = ''; private isActive = false; private segments: MessageSegment[] = []; private currentTextSegment: MessageSegment | null = null; constructor(extensionPath: string, existingTaskId?: string) { // 支持复用现有 taskId(用于 Plan 模式确认后继续执行) this.taskId = existingTaskId || generateTaskId(); this.toolContext = createToolExecutorContext(extensionPath); } /** * 添加文本到当前文本段落 */ private appendText(text: string): void { if (!this.currentTextSegment) { this.currentTextSegment = { type: 'text', content: '' }; this.segments.push(this.currentTextSegment); } this.currentTextSegment.content = (this.currentTextSegment.content || '') + text; } /** * 结束当前文本段落 */ private finalizeTextSegment(): void { this.currentTextSegment = null; } /** * 添加工具段落 */ private addToolSegment(toolName: string, status: 'running' | 'success' | 'error', result?: string): MessageSegment { this.finalizeTextSegment(); const segment: MessageSegment = { type: 'tool', toolName, toolStatus: status, toolResult: result }; this.segments.push(segment); return segment; } /** * 更新工具段落状态 */ private updateToolSegment(toolName: string, status: 'success' | 'error', result?: string): void { // 找到最后一个匹配的工具段落 for (let i = this.segments.length - 1; i >= 0; i--) { const seg = this.segments[i]; if (seg.type === 'tool' && seg.toolName === toolName && seg.toolStatus === 'running') { seg.toolStatus = status; seg.toolResult = result; break; } } } /** * 获取任务ID */ getTaskId(): string { return this.taskId; } /** * 是否活跃 */ get active(): boolean { return this.isActive; } /** * 加载知识图谱数据 * 从 .iccoder/knowledge.json 读取 */ private loadKnowledgeData(): string | null { console.log('[DialogSession] loadKnowledgeData 开始执行'); const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { console.log('[DialogSession] 没有工作区文件夹'); return null; } const workspacePath = workspaceFolders[0].uri.fsPath; const knowledgePath = path.join(workspacePath, '.iccoder', 'knowledge.json'); console.log('[DialogSession] 知识图谱路径:', knowledgePath); try { const exists = fs.existsSync(knowledgePath); console.log('[DialogSession] 文件存在:', exists); if (exists) { const content = fs.readFileSync(knowledgePath, 'utf-8'); console.log('[DialogSession] 加载知识图谱成功, 长度:', content.length); return content; } } catch (error) { console.warn('[DialogSession] 加载知识图谱失败:', error); } return null; } /** * 获取工具操作描述(用于确认对话框) */ private getToolDescription(toolName: string, toolInput: Record): string { const lines: string[] = []; switch (toolName) { case 'file_write': lines.push(`文件路径: ${toolInput.path || '未知'}`); if (toolInput.content) { const content = String(toolInput.content); lines.push(`内容长度: ${content.length} 字符`); lines.push(`内容预览: ${content.substring(0, 100)}${content.length > 100 ? '...' : ''}`); } break; case 'file_delete': lines.push(`删除文件: ${toolInput.path || '未知'}`); break; case 'syntax_check': lines.push('执行语法检查'); if (toolInput.code) { const code = String(toolInput.code); lines.push(`代码长度: ${code.length} 字符`); } break; case 'simulation': lines.push(`RTL文件: ${toolInput.rtlPath || '未知'}`); lines.push(`TB文件: ${toolInput.tbPath || '未知'}`); if (toolInput.duration) { lines.push(`仿真时长: ${toolInput.duration}`); } break; default: lines.push(`参数: ${JSON.stringify(toolInput, null, 2)}`); } return lines.join('\n'); } /** * 发送消息并开始流式对话 */ async sendMessage( message: string, callbacks: DialogCallbacks, mode?: RunMode ): Promise { if (this.isActive) { callbacks.onError?.('当前有对话正在进行中'); return; } this.isActive = true; this.accumulatedText = ''; this.segments = []; this.currentTextSegment = null; const config = getConfig(); // 获取压缩数据和新消息(用于后端重启后恢复) const historyManager = ChatHistoryManager.getInstance(); const compactedData = await historyManager.loadCompactedData(this.taskId); const newMessages = historyManager.getNewMessagesSinceCompaction(); // 加载知识图谱数据 const knowledgeData = this.loadKnowledgeData(); console.log('[DialogSession] knowledgeData 加载结果:', knowledgeData ? `${knowledgeData.length} 字符` : 'null'); const request: DialogRequest = { taskId: this.taskId, message, userId: config.userId, mode: mode || 'agent', compactedData: compactedData || undefined, newMessages: newMessages.length > 0 ? newMessages : undefined, knowledgeData: knowledgeData || undefined }; // 追踪用户消息 historyManager.trackUserMessage(message); const sseCallbacks: SSECallbacks = { onTextDelta: (data) => { this.accumulatedText += data.text; this.appendText(data.text); console.log('[DialogSession] onTextDelta, 累积文本长度:', this.accumulatedText.length); callbacks.onText?.(this.accumulatedText, true); // 实时发送段落更新 callbacks.onSegmentUpdate?.(this.segments); }, onToolCall: async (data: ToolCallRequest) => { const toolName = data.params.name; console.log('[DialogSession] onToolCall:', toolName); // 检查是否有活跃的智能体(如果有,工具执行会显示在智能体卡片内,不需要单独显示) const hasActiveAgent = this.segments.some( s => s.type === 'agent' && s.agentStatus === 'running' ); if (hasActiveAgent) { console.log('[DialogSession] onToolCall: 智能体执行中,跳过工具段落:', toolName); } else { // 检查是否已经有相同的工具段落(可能由 onToolStart 添加) const lastToolSegment = this.segments.filter(s => s.type === 'tool').pop(); if (lastToolSegment && lastToolSegment.toolName === toolName && lastToolSegment.toolStatus === 'running') { console.log('[DialogSession] onToolCall: 跳过重复的工具段落:', toolName); } else { this.addToolSegment(toolName, 'running'); // 实时发送段落更新 callbacks.onSegmentUpdate?.(this.segments); } } // 注意:不在这里调用 callbacks.onToolStart,避免与 onToolStart 事件重复 try { await executeToolCall(data, this.toolContext); if (!hasActiveAgent) { this.updateToolSegment(toolName, 'success', '执行完成'); // 实时发送段落更新 callbacks.onSegmentUpdate?.(this.segments); } // 也不调用 callbacks.onToolComplete,避免重复 } catch (error) { const errorMsg = error instanceof Error ? error.message : '未知错误'; if (!hasActiveAgent) { this.updateToolSegment(toolName, 'error', errorMsg); callbacks.onSegmentUpdate?.(this.segments); } callbacks.onToolError?.(toolName, errorMsg); } }, onToolStart: (data) => { console.log('[DialogSession] onToolStart:', data.tool_name); // 检查是否已经有相同的工具段落(可能由 onToolCall 添加) const lastToolSegment = this.segments.filter(s => s.type === 'tool').pop(); if (lastToolSegment && lastToolSegment.toolName === data.tool_name && lastToolSegment.toolStatus === 'running') { console.log('[DialogSession] 跳过重复的工具段落:', data.tool_name); } else { this.addToolSegment(data.tool_name, 'running'); // 实时发送段落更新 callbacks.onSegmentUpdate?.(this.segments); } console.log('[DialogSession] segments 数量:', this.segments.length); callbacks.onToolStart?.(data.tool_name); }, onToolComplete: (data) => { this.updateToolSegment(data.tool_name, 'success', data.result); callbacks.onToolComplete?.(data.tool_name, data.result); // 实时发送段落更新 callbacks.onSegmentUpdate?.(this.segments); }, onToolError: (data) => { this.updateToolSegment(data.tool_name, 'error', data.error); callbacks.onToolError?.(data.tool_name, data.error); // 实时发送段落更新 callbacks.onSegmentUpdate?.(this.segments); }, onToolConfirm: async (data: ToolConfirmEvent) => { console.log('[DialogSession] onToolConfirm:', data.toolName, data.confirmId); // 调用回调通知 UI 显示确认对话框 callbacks.onToolConfirm?.(data.confirmId, data.toolName, data.toolInput); // 使用 VSCode 快速选择框显示确认对话框 const toolDescription = this.getToolDescription(data.toolName, data.toolInput); const result = await vscode.window.showWarningMessage( `确认执行操作: ${data.toolName}`, { modal: true, detail: toolDescription }, '确认执行', '取消' ); const approved = result === '确认执行'; console.log('[DialogSession] 用户确认结果:', approved); // 发送确认响应到后端 try { await submitToolConfirm({ confirmId: data.confirmId, taskId: this.taskId, approved }); } catch (error) { console.error('[DialogSession] 发送确认响应失败:', error); } }, onPlanConfirm: async (data: PlanConfirmEvent) => { console.log('[DialogSession] onPlanConfirm:', data.title); // 结束当前文本段落 this.finalizeTextSegment(); const askId = `ask_${data.confirmId}`; // 添加计划段落到聊天界面(包含 askId 用于响应) this.segments.push({ type: 'plan', askId: askId, planTitle: data.title, planSteps: data.steps, planSummary: data.summary }); // 实时发送段落更新 callbacks.onSegmentUpdate?.(this.segments); // 注册问题到前端(类似 askUser),以便用户回答时能找到 const planEvent = { askId: askId, question: `请确认执行计划:${data.title}`, options: ['确认执行', '修改计划', '取消'] }; try { await userInteractionManager.handleAskUser(planEvent as AskUserEvent, this.taskId); } catch (error) { console.error('[DialogSession] 处理计划确认失败:', error); } // 调用回调通知 UI callbacks.onPlanConfirm?.(data.confirmId, data.title, data.steps, data.summary); }, onAskUser: async (data: AskUserEvent) => { this.finalizeTextSegment(); this.segments.push({ type: 'question', askId: data.askId, question: data.question, options: data.options }); // 实时发送段落更新(包含问题) callbacks.onSegmentUpdate?.(this.segments); // 同时调用 onQuestion 用于更新状态栏等 callbacks.onQuestion?.(data.askId, data.question, data.options); try { await userInteractionManager.handleAskUser(data, this.taskId); } catch (error) { console.error('[DialogSession] 处理用户问题失败:', error); } }, onComplete: (data) => { this.isActive = false; this.finalizeTextSegment(); // 发送所有段落 callbacks.onComplete?.(this.segments); }, onError: (data) => { this.isActive = false; callbacks.onError?.(data.message); }, onWarning: (data) => { callbacks.onNotification?.(`⚠️ ${data.message}`); }, onNotification: (data) => { callbacks.onNotification?.(data.message); }, // 智能体事件处理 onAgentStart: (data) => { console.log('[DialogSession] onAgentStart:', data.agentId); this.finalizeTextSegment(); this.segments.push({ type: 'agent', agentId: data.agentId, agentName: data.agentName, content: data.instruction, agentStatus: 'running', agentSteps: [] }); callbacks.onSegmentUpdate?.(this.segments); }, onAgentProgress: (data) => { console.log('[DialogSession] onAgentProgress:', data.agentId, data.step, data.status); const agentSegment = this.segments.find( s => s.type === 'agent' && s.agentId === data.agentId ); if (agentSegment && agentSegment.agentSteps) { if (data.status === 'running') { agentSegment.agentSteps.push({ step: data.step, toolName: data.toolName, toolInput: data.toolInput, status: 'running' }); } else { const step = agentSegment.agentSteps.find(s => s.step === data.step); if (step) { step.status = data.status; step.toolResult = data.toolResult; } } callbacks.onSegmentUpdate?.(this.segments); } }, onAgentComplete: (data) => { console.log('[DialogSession] onAgentComplete:', data.agentId); const agentSegment = this.segments.find( s => s.type === 'agent' && s.agentId === data.agentId ); if (agentSegment) { agentSegment.agentStatus = 'completed'; agentSegment.content = data.summary; callbacks.onSegmentUpdate?.(this.segments); } }, onAgentError: (data) => { console.log('[DialogSession] onAgentError:', data.agentId, data.error); const agentSegment = this.segments.find( s => s.type === 'agent' && s.agentId === data.agentId ); if (agentSegment) { agentSegment.agentStatus = 'error'; agentSegment.content = data.error; callbacks.onSegmentUpdate?.(this.segments); } }, onMemoryCompacted: async (data) => { console.log('[DialogSession] onMemoryCompacted:', data.taskId); // 保存压缩数据到本地 await historyManager.saveCompactedData(data.compactedData); }, onOpen: () => { console.log('[DialogSession] SSE 连接已建立'); }, onClose: () => { console.log('[DialogSession] SSE 连接已关闭'); this.isActive = false; } }; try { this.sseController = await startStreamDialog(request, sseCallbacks); } catch (error) { this.isActive = false; const errorMsg = error instanceof Error ? error.message : '连接失败'; callbacks.onError?.(errorMsg); throw error; } } /** * 中止当前对话 */ abort(): void { if (this.sseController) { this.sseController.abort(); this.sseController = null; } this.isActive = false; userInteractionManager.cancelAll(); } /** * 提交用户回答 */ async submitAnswer( askId: string, selected?: string[], customInput?: string ): Promise { await userInteractionManager.receiveAnswer(askId, selected, customInput); } } /** * 全局对话会话管理 */ class DialogManager { private currentSession: DialogSession | null = null; /** * 创建新会话 * @param extensionPath 扩展路径 * @param existingTaskId 可选,复用现有的 taskId(用于 Plan 模式确认后继续执行) */ createSession(extensionPath: string, existingTaskId?: string): DialogSession { // 如果有活跃会话,先中止 if (this.currentSession?.active) { this.currentSession.abort(); } this.currentSession = new DialogSession(extensionPath, existingTaskId); return this.currentSession; } /** * 获取当前会话 */ getCurrentSession(): DialogSession | null { return this.currentSession; } /** * 中止当前会话 */ abortCurrentSession(): void { this.currentSession?.abort(); } } export const dialogManager = new DialogManager();