/** * 对话服务 * 整合 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, ServiceTier, ToolConfirmEvent, PlanConfirmEvent, } from "../types/api"; import { submitToolConfirm, submitAnswer, stopDialog } from "./apiClient"; import { getActiveRules } from "../utils/personalRulesManager"; import { ChatHistoryManager } from "../utils/chatHistoryManager"; import { getUserIdFromToken, isTokenExpired } from "../utils/jwtUtils"; import { updateCachedBalance } from "./creditsService"; /** * 消息段落类型 */ export interface MessageSegment { type: "text" | "tool" | "question" | "agent" | "plan" | "progress"; content?: string; toolName?: string; toolStatus?: "running" | "success" | "error"; toolResult?: string; toolDescription?: string; askId?: string; questions?: import("../types/api").QuestionItem[]; // 智能体相关字段 agentId?: string; agentName?: string; agentStatus?: "running" | "completed" | "error"; agentSteps?: AgentStep[]; // 计划相关字段 planTitle?: string; planPhases?: import("../types/api").PlanPhase[]; planSteps?: string[]; planSummary?: string; // 进度条相关字段(独立于 plan,用于执行模式) progressPhases?: import("../types/api").PlanPhase[]; } /** * 智能体执行步骤 */ 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, phases: import("../types/api").PlanPhase[] | undefined, steps: string[] | undefined, summary: string ) => void; /** 显示问题(ask_user) */ onQuestion?: (askId: string, questions: import("../types/api").QuestionItem[]) => void; /** 实时更新段落(流式过程中) */ onSegmentUpdate?: (segments: MessageSegment[]) => void; /** 对话完成,返回所有段落 */ onComplete?: (segments: MessageSegment[]) => void; /** 错误 */ onError?: (message: string) => void; /** 通知消息 */ onNotification?: (message: string) => void; /** 上下文使用量更新 */ onContextUsage?: (data: { currentTokens: number; maxTokens: number; percentage: number; }) => void; /** 阶段进度更新 */ onPhaseProgress?: (phaseId: string, status: string) => void; } /** * 对话会话 */ export class DialogSession { private taskId: string; private sseController: SSEController | null = null; private toolContext: ToolExecutorContext; private accumulatedText = ""; private isActive = false; private hasCompleted = false; // 标记是否已收到 complete 事件 private segments: MessageSegment[] = []; private currentTextSegment: MessageSegment | null = null; private completeCallback: ((segments: MessageSegment[]) => void) | null = null; // 保存完成回调,用于 abort 时触发 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, description?: 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; if (description !== undefined) { seg.toolDescription = description; } break; } } } /** * 获取任务ID */ getTaskId(): string { return this.taskId; } /** * 是否活跃 */ get active(): boolean { return this.isActive; } /** * 加载知识图谱数据 * 从 .iccoder/knowledge.json 读取 */ private async loadKnowledgeData(): Promise { console.log("[DialogSession] loadKnowledgeData 开始执行"); // 等待 workspaceFolders 就绪(首次打开窗口/首次触发命令时可能为空) const workspaceFolders = await this.waitForWorkspaceFolders(); if (!workspaceFolders || workspaceFolders.length === 0) { console.log("[DialogSession] 没有工作区文件夹"); return null; } // 多根工作区场景:优先读取实际存在 knowledge.json 的根目录 for (const folder of this.getWorkspaceFolderCandidates(workspaceFolders)) { const knowledgeUri = vscode.Uri.joinPath( folder.uri, ".iccoder", "knowledge.json" ); console.log("[DialogSession] 知识图谱 URI:", knowledgeUri.toString()); try { const content = await this.readTextFileWithRetry(knowledgeUri, 5); if (!content) { continue; } // 基础校验 + 清洗:避免偶发读取到半截内容导致后端反序列化失败 try { const parsed = JSON.parse(content) as any; // 兼容:后端 KnowledgeGraph.isEmpty() 可能被序列化为 "empty",老后端反序列化会失败 if (parsed && typeof parsed === "object" && "empty" in parsed) { delete parsed.empty; } const sanitized = JSON.stringify(parsed); console.log( "[DialogSession] 知识图谱已清洗, sanitizedLen:", sanitized.length ); return sanitized; } catch (e) { console.warn( "[DialogSession] 知识图谱 JSON 解析失败,跳过本次读取:", e ); continue; } } catch (error) { console.warn("[DialogSession] 加载知识图谱失败:", error); } } return null; } private async waitForWorkspaceFolders(): Promise< readonly vscode.WorkspaceFolder[] | undefined > { for (let i = 0; i < 10; i++) { const folders = vscode.workspace.workspaceFolders; if (folders && folders.length > 0) { return folders; } await new Promise((resolve) => setTimeout(resolve, 100)); } return vscode.workspace.workspaceFolders; } private getWorkspaceFolderCandidates( workspaceFolders: readonly vscode.WorkspaceFolder[] ): vscode.WorkspaceFolder[] { const result: vscode.WorkspaceFolder[] = []; // 1) 当前激活文件所在的 workspace folder(如果有) const activeUri = vscode.window.activeTextEditor?.document?.uri; const activeFolder = activeUri ? vscode.workspace.getWorkspaceFolder(activeUri) : undefined; if (activeFolder) { result.push(activeFolder); } // 2) 其它 workspace folders(去重) for (const folder of workspaceFolders) { if (!result.some((f) => f.uri.toString() === folder.uri.toString())) { result.push(folder); } } return result; } private async readTextFileWithRetry( uri: vscode.Uri, maxAttempts: number ): Promise { for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { const bytes = await vscode.workspace.fs.readFile(uri); const text = Buffer.from(bytes).toString("utf-8"); if (!text || !text.trim()) { return null; } return text; } catch (error) { // 文件不存在:不是错误,直接返回 null if ( error instanceof vscode.FileSystemError && error.code === "FileNotFound" ) { return null; } const retryable = (error instanceof vscode.FileSystemError && error.code === "Unavailable") || (typeof (error as any)?.code === "string" && ["EBUSY", "EPERM", "EACCES"].includes((error as any).code)); if (!retryable || attempt >= maxAttempts) { throw error; } const delayMs = 50 * attempt; console.log( `[DialogSession] 读取知识图谱失败(可重试): attempt=${attempt}/${maxAttempts}, delay=${delayMs}ms` ); await new Promise((resolve) => setTimeout(resolve, delayMs)); } } 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, serviceTier?: ServiceTier // 新增:服务等级参数 ): Promise { if (this.isActive) { callbacks.onError?.("当前有对话正在进行中"); return; } this.isActive = true; this.hasCompleted = false; // 重置完成标志 this.accumulatedText = ""; this.segments = []; this.currentTextSegment = null; this.completeCallback = callbacks.onComplete || null; // 保存完成回调,用于 abort 时触发 const config = getConfig(); // 从登录 session 获取真实 userId 和 token let userId = config.userId; // 默认值 let token: string | undefined; try { console.log("[DialogSession] 尝试获取登录 session..."); const session = await vscode.authentication.getSession("iccoder", [], { silent: true, }); console.log( "[DialogSession] session 结果:", session ? "已获取" : "null/undefined" ); if (session?.accessToken) { console.log( "[DialogSession] accessToken 长度:", session.accessToken.length ); // 检测 token 是否过期 const expired = isTokenExpired(session.accessToken); if (expired === true) { console.error("[DialogSession] token 已过期,需要重新登录"); vscode.window .showErrorMessage("登录已过期,请重新登录", "重新登录") .then((selection) => { if (selection === "重新登录") { vscode.commands.executeCommand("ic-coder.login", { forceReauth: true, }); } }); throw new Error("登录已过期,请重新登录"); } token = session.accessToken; // 保存 token 用于扣费 const parsedUserId = getUserIdFromToken(session.accessToken); console.log("[DialogSession] 解析的 userId:", parsedUserId); if (parsedUserId) { userId = parsedUserId; console.log("[DialogSession] 使用真实 userId:", userId); } } else { console.log( "[DialogSession] 未获取到 accessToken,使用默认 userId:", userId ); } } catch (error) { console.warn("[DialogSession] 获取登录 session 失败:", error); } // 获取压缩数据和新消息(用于后端重启后恢复) const historyManager = ChatHistoryManager.getInstance(); const compactedData = await historyManager.loadCompactedData(this.taskId); const newMessages = historyManager.getNewMessagesSinceCompaction(); // 加载知识图谱数据 const knowledgeData = await this.loadKnowledgeData(); console.log( "[DialogSession] knowledgeData 加载结果:", knowledgeData ? `${knowledgeData.length} 字符` : "null" ); console.log( "[DialogSession] serviceTier 参数:", serviceTier, "-> 使用:", serviceTier || config.serviceTier ); const request: DialogRequest = { taskId: this.taskId, message, userId, mode: mode || "agent", serviceTier: serviceTier || config.serviceTier, // 优先使用传入的参数 token, // JWT token 用于扣费 compactedData: compactedData || undefined, newMessages: newMessages.length > 0 ? newMessages : undefined, knowledgeData: knowledgeData || undefined, personalRules: getActiveRules() || 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, data.description); callbacks.onToolComplete?.(data.tool_name, data.result); // 实时发送段落更新 callbacks.onSegmentUpdate?.(this.segments); // 追踪工具执行结果(用于后端重启后恢复) historyManager.trackToolResult(data.tool_name, data.result); }, onToolError: (data) => { this.updateToolSegment(data.tool_name, "error", data.error); callbacks.onToolError?.(data.tool_name, data.error); // 实时发送段落更新 callbacks.onSegmentUpdate?.(this.segments); // 追踪工具执行错误(用于后端重启后恢复) historyManager.trackToolResult(data.tool_name, `[错误] ${data.error}`); }, onToolConfirm: async (data: ToolConfirmEvent) => { console.log( "[DialogSession] onToolConfirm:", data.toolName, data.confirmId ); // 结束当前文本段落 this.finalizeTextSegment(); // 生成工具描述 const toolDescription = this.getToolDescription( data.toolName, data.toolInput ); // 构建问题文本 const toolNameMap: Record = { file_write: "写入文件", file_delete: "删除文件", syntax_check: "语法检查", simulation: "运行仿真", }; const toolDisplayName = toolNameMap[data.toolName] || data.toolName; const question = `确认执行操作:${toolDisplayName}\n\n${toolDescription}`; // 生成唯一的 askId const askId = `tool_confirm_${data.confirmId}`; // 添加问题段落到聊天界面 this.segments.push({ type: "question", askId: askId, questions: [{ question: question, options: ["确认执行", "取消"], multiSelect: false }], }); // 实时发送段落更新 callbacks.onSegmentUpdate?.(this.segments); // 调用回调通知 UI callbacks.onToolConfirm?.( data.confirmId, data.toolName, data.toolInput ); // 使用 userInteractionManager 等待用户回答 try { await userInteractionManager.handleAskUser( { askId: askId, questions: [{ question: question, options: ["确认执行", "取消"], multiSelect: false }] } as AskUserEvent, this.taskId ); // 注意:用户回答后,需要在 receiveAnswer 中处理 tool_confirm 类型的 askId // 这里不直接调用 submitToolConfirm,而是在 userInteractionManager 中统一处理 } catch (error) { console.error("[DialogSession] 处理工具确认失败:", error); // 如果出错,默认取消执行 try { await submitToolConfirm({ confirmId: data.confirmId, taskId: this.taskId, approved: false, }); } catch (submitError) { console.error("[DialogSession] 发送取消响应失败:", submitError); } } }, onPlanConfirm: async (data: PlanConfirmEvent) => { console.log("[DialogSession] onPlanConfirm:", data.title); // 结束当前文本段落 this.finalizeTextSegment(); const askId = `ask_${data.confirmId}`; // 添加计划段落到聊天界面(包含 askId 用于响应) // 支持新格式(phases)和旧格式(steps) this.segments.push({ type: "plan", askId: askId, planTitle: data.title, planPhases: data.phases, planSteps: data.steps, planSummary: data.summary, }); // 实时发送段落更新 callbacks.onSegmentUpdate?.(this.segments); // 注册问题到前端(类似 askUser),以便用户回答时能找到 const planEvent = { askId: askId, questions: [{ question: `请确认执行计划:${data.title}`, options: ["确认执行", "修改计划", "取消"], multiSelect: false }] }; try { await userInteractionManager.handleAskUser( planEvent as AskUserEvent, this.taskId ); } catch (error) { console.error("[DialogSession] 处理计划确认失败:", error); } // 调用回调通知 UI callbacks.onPlanConfirm?.( data.confirmId, data.title, data.phases, data.steps, data.summary ); }, onPhaseProgress: (data: import("../types/api").PhaseProgressEvent) => { console.log( "[DialogSession] onPhaseProgress:", data.phaseId, data.status ); // 1. 尝试更新 plan segment(兼容旧逻辑) for (let i = this.segments.length - 1; i >= 0; i--) { const seg = this.segments[i]; if (seg.type === "plan" && seg.planPhases) { seg.planPhases = seg.planPhases.map((phase) => { if (phase.id === data.phaseId) { return { ...phase, status: data.status }; } return phase; }); callbacks.onSegmentUpdate?.(this.segments); break; } } // 2. 通知外部更新独立进度条 callbacks.onPhaseProgress?.(data.phaseId, data.status); }, onPlanStepAdd: (data: import("../types/api").PlanStepAddEvent) => { console.log("[DialogSession] onPlanStepAdd:", data.phaseId, data.step); for (let i = this.segments.length - 1; i >= 0; i--) { const seg = this.segments[i]; if (seg.type === "plan" && seg.planPhases) { seg.planPhases = seg.planPhases.map((phase) => { if (phase.id === data.phaseId) { const newSteps = [...(phase.steps || [])]; if (data.index >= 0 && data.index < newSteps.length) { newSteps.splice(data.index, 0, data.step); } else { newSteps.push(data.step); } return { ...phase, steps: newSteps }; } return phase; }); break; } } callbacks.onSegmentUpdate?.(this.segments); }, onPlanStepRemove: (data: import("../types/api").PlanStepRemoveEvent) => { console.log( "[DialogSession] onPlanStepRemove:", data.phaseId, data.stepIndex ); for (let i = this.segments.length - 1; i >= 0; i--) { const seg = this.segments[i]; if (seg.type === "plan" && seg.planPhases) { seg.planPhases = seg.planPhases.map((phase) => { if (phase.id === data.phaseId && phase.steps) { const newSteps = [...phase.steps]; newSteps.splice(data.stepIndex, 1); return { ...phase, steps: newSteps }; } return phase; }); break; } } callbacks.onSegmentUpdate?.(this.segments); }, onPlanStepUpdate: (data: import("../types/api").PlanStepUpdateEvent) => { console.log( "[DialogSession] onPlanStepUpdate:", data.phaseId, data.stepIndex ); for (let i = this.segments.length - 1; i >= 0; i--) { const seg = this.segments[i]; if (seg.type === "plan" && seg.planPhases) { seg.planPhases = seg.planPhases.map((phase) => { if (phase.id === data.phaseId && phase.steps) { const newSteps = [...phase.steps]; if (data.stepIndex >= 0 && data.stepIndex < newSteps.length) { newSteps[data.stepIndex] = data.step; } return { ...phase, steps: newSteps }; } return phase; }); break; } } callbacks.onSegmentUpdate?.(this.segments); }, onPlanSummaryUpdate: ( data: import("../types/api").PlanSummaryUpdateEvent ) => { console.log("[DialogSession] onPlanSummaryUpdate"); for (let i = this.segments.length - 1; i >= 0; i--) { const seg = this.segments[i]; if (seg.type === "plan") { seg.planSummary = data.summary; break; } } callbacks.onSegmentUpdate?.(this.segments); }, onAskUser: async (data: AskUserEvent) => { this.finalizeTextSegment(); this.segments.push({ type: "question", askId: data.askId, questions: data.questions, }); // 实时发送段落更新(包含问题) callbacks.onSegmentUpdate?.(this.segments); // 同时调用 onQuestion 用于更新状态栏等 callbacks.onQuestion?.(data.askId, data.questions); try { await userInteractionManager.handleAskUser(data, this.taskId); } catch (error) { console.error("[DialogSession] 处理用户问题失败:", error); } }, onComplete: (data) => { this.isActive = false; this.hasCompleted = true; // 标记已收到 complete 事件 this.finalizeTextSegment(); // 追踪 AI 消息(用于后端重启后恢复) if (this.accumulatedText) { historyManager.trackAiMessage(this.accumulatedText); } // 发送所有段落 callbacks.onComplete?.(this.segments); }, onError: (data) => { this.isActive = false; // 检测登录状态过期(只弹一次窗,不再传递错误) if ( data.message.includes("LOGIN_EXPIRED") || data.message.includes("登录状态已过期") ) { vscode.window .showErrorMessage("登录状态已过期,请重新登录", "重新登录") .then((selection) => { if (selection === "重新登录") { vscode.commands.executeCommand("ic-coder.login", { forceReauth: true, }); } }); // 登录过期错误已处理,不再传递给外部 return; } 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); }, onContextUsage: (data) => { console.log( "[DialogSession] onContextUsage:", data.currentTokens, "/", data.maxTokens ); callbacks.onContextUsage?.(data); }, onCreditUpdate: (data) => { console.log( "[DialogSession] onCreditUpdate: 扣除", data.deductedCredits, "剩余", data.remainingCredits ); // 更新余额缓存 updateCachedBalance(data.remainingCredits); // 资源点余额低于阈值时弹窗提醒 const LOW_CREDIT_THRESHOLD = 5; if (data.remainingCredits < LOW_CREDIT_THRESHOLD) { vscode.window .showWarningMessage( `资源点余额不足!当前剩余 ${data.remainingCredits.toFixed( 2 )} 点,请及时充值。`, "去充值" ) .then((selection) => { if (selection === "去充值") { // 打开充值页面 vscode.env.openExternal( vscode.Uri.parse("https://iccoder.com/recharge") ); } }); } }, onOpen: () => { console.log("[DialogSession] SSE 连接已建立"); }, onClose: () => { console.log("[DialogSession] SSE 连接已关闭"); // 如果没有收到 complete 事件,需要补充完成逻辑 if (!this.hasCompleted && this.isActive) { console.log("[DialogSession] 未收到 complete 事件,补充完成处理"); this.finalizeTextSegment(); if (this.accumulatedText) { historyManager.trackAiMessage(this.accumulatedText); } callbacks.onComplete?.(this.segments); } 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 { // 先标记完成,防止 onClose 重复触发 const wasActive = this.isActive; this.hasCompleted = true; this.isActive = false; if (this.sseController) { this.sseController.abort(); this.sseController = null; } userInteractionManager.cancelAll(); // 如果之前是活跃状态,触发完成回调以结束 Promise if (wasActive && this.completeCallback) { this.finalizeTextSegment(); console.log("[DialogSession] abort 触发完成回调"); this.completeCallback(this.segments); this.completeCallback = null; } // 通知后端停止处理 stopDialog(this.taskId).catch((err) => { console.warn("[DialogSession] 停止对话请求失败:", err); }); } /** * 获取当前的消息段落(用于中止时保存) */ getSegments(): MessageSegment[] { return this.segments; } /** * 获取累积的文本内容 */ getAccumulatedText(): string { return this.accumulatedText; } /** * 提交用户回答 */ async submitAnswer( askId: string, selected?: string[], customInput?: string, answers?: { [questionIndex: string]: string[] } ): Promise { // 直接调用 receiveAnswer,传递 taskId 作为 fallbackTaskId // 如果 pendingQuestions 中有问题,走正常流程 // 如果没有,receiveAnswer 会使用 fallbackTaskId 直接发送到后端 await userInteractionManager.receiveAnswer( askId, selected, customInput, answers, this.taskId ); } } /** * 全局对话会话管理 */ 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(); this.currentSession = null; // 清空会话,确保下次创建新会话 } } export const dialogManager = new DialogManager();