/** * 工具执行器 * 接收后端的 tool_call 事件,执行本地工具,返回结果 */ import * as vscode from 'vscode'; import * as path from 'path'; import * as os from 'os'; import * as fs from 'fs'; import { readFileContent, readDirectory } from '../utils/readFiles'; import { createOrOverwriteFile } from '../utils/createFiles'; import { generateVCD, checkIverilogAvailable, generateMultiVCD, DumpModule } from '../utils/iverilogRunner'; import { analyzeVcdFile } from '../utils/vcdParser'; import { executeWaveformTrace, WaveformTraceArgs } from '../utils/waveformTracer'; import { submitToolResult, createSuccessResult, createBusinessErrorResult, createSystemErrorResult } from './apiClient'; import type { ToolCallRequest, ToolName, FileReadArgs, FileWriteArgs, FileDeleteArgs, FileListArgs, SyntaxCheckArgs, IverilogArgs, SimulationArgs, WaveformSummaryArgs, KnowledgeSaveArgs, KnowledgeLoadArgs } from '../types/api'; /** * 工具执行器上下文 */ export interface ToolExecutorContext { /** 扩展路径(用于 iverilog) */ extensionPath: string; /** 工作区路径 */ workspacePath: string; } /** * 执行工具调用 * @param request 工具调用请求 * @param context 执行上下文 */ export async function executeToolCall( request: ToolCallRequest, context: ToolExecutorContext ): Promise { const toolName = request.params.name as ToolName; const args = request.params.arguments; const callId = request.id; console.log(`[ToolExecutor] 执行工具: ${toolName}, callId=${callId}`, args); try { let resultText: string; switch (toolName) { case 'file_read': resultText = await executeFileRead(args as unknown as FileReadArgs); break; case 'file_write': resultText = await executeFileWrite(args as unknown as FileWriteArgs); break; case 'file_delete': resultText = await executeFileDelete(args as unknown as FileDeleteArgs); break; case 'file_list': resultText = await executeFileList(args as unknown as FileListArgs); break; case 'syntax_check': resultText = await executeSyntaxCheck(args as unknown as SyntaxCheckArgs, context); break; case 'iverilog': resultText = await executeIverilog(args as unknown as IverilogArgs, context); break; case 'simulation': resultText = await executeSimulation(args as unknown as SimulationArgs, context); break; case 'waveform_summary': resultText = await executeWaveformSummary(args as unknown as WaveformSummaryArgs); break; case 'waveform_trace': resultText = await executeWaveformTrace(args as unknown as WaveformTraceArgs, context); break; case 'knowledge_save': resultText = await executeKnowledgeSave(args as unknown as KnowledgeSaveArgs); break; case 'knowledge_load': resultText = await executeKnowledgeLoad(); break; default: throw new Error(`未知工具: ${toolName}`); } // 提交成功结果 const result = createSuccessResult(callId, resultText); await submitToolResult(result); console.log(`[ToolExecutor] 工具执行成功: ${toolName}, callId=${callId}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误'; console.error(`[ToolExecutor] 工具执行失败: ${toolName}, callId=${callId}`, error); // 提交错误结果 const result = createBusinessErrorResult(callId, errorMessage); await submitToolResult(result); } } /** * 执行 file_read 工具 */ async function executeFileRead(args: FileReadArgs): Promise { const content = await readFileContent(args.path); return content; } /** * 执行 file_write 工具 */ async function executeFileWrite(args: FileWriteArgs): Promise { await createOrOverwriteFile(args.path, args.content); // Verilog 文件添加知识图谱提示 const isVerilogFile = args.path.endsWith('.v') || args.path.endsWith('.sv'); if (isVerilogFile) { return `文件已写入: ${args.path}\n\n[提示] 如有新信号或规则,请更新知识图谱`; } return `文件已写入: ${args.path}`; } /** * 执行 file_delete 工具 * 删除指定路径的文件 */ async function executeFileDelete(args: FileDeleteArgs): Promise { const filePath = args.path; // 获取工作区路径 const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { throw new Error('请先打开一个工作区'); } const workspacePath = workspaceFolders[0].uri.fsPath; // 解析文件路径(支持相对路径和绝对路径) const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(workspacePath, filePath); // 检查文件是否存在 if (!fs.existsSync(absolutePath)) { throw new Error(`文件不存在: ${filePath}`); } // 检查是否为文件(不允许删除目录) const stat = fs.statSync(absolutePath); if (stat.isDirectory()) { throw new Error(`不能删除目录,请指定文件路径: ${filePath}`); } // 删除文件 fs.unlinkSync(absolutePath); // Verilog 文件添加知识图谱提示 const isVerilogFile = filePath.endsWith('.v') || filePath.endsWith('.sv'); if (isVerilogFile) { return `文件已删除: ${filePath}\n\n[提示] 请删除知识图谱中相关节点`; } return `文件已删除: ${filePath}`; } /** * 执行 file_list 工具 */ async function executeFileList(args: FileListArgs): Promise { const dirPath = args.path || '.'; const extensions = args.extension ? [args.extension] : undefined; const files = await readDirectory(dirPath, extensions); const fileList = files.map(f => f.path).join('\n'); return fileList || '(目录为空)'; } /** * 执行 syntax_check 工具 * 将代码写入临时文件,调用 iverilog 检查语法 */ async function executeSyntaxCheck( args: SyntaxCheckArgs, context: ToolExecutorContext ): Promise { // 检查 iverilog 是否可用 const iverilogCheck = await checkIverilogAvailable(context.extensionPath); if (!iverilogCheck.available) { throw new Error(`iverilog 不可用: ${iverilogCheck.message}`); } // 创建临时文件 const tempDir = os.tmpdir(); const tempFile = path.join(tempDir, `iccoder_syntax_${Date.now()}.v`); try { // 写入代码到临时文件 fs.writeFileSync(tempFile, args.code, 'utf-8'); // 调用 iverilog 进行语法检查 const { spawn } = require('child_process'); const iverilogPath = getIverilogPath(context.extensionPath); return new Promise((resolve, reject) => { const child = spawn(iverilogPath, ['-t', 'null', tempFile], { cwd: tempDir, env: { ...process.env, IVERILOG_ROOT: path.join(context.extensionPath, 'tools', 'iverilog') } }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data: Buffer) => { stdout += data.toString(); }); child.stderr.on('data', (data: Buffer) => { stderr += data.toString(); }); child.on('close', (code: number) => { // 清理临时文件 try { fs.unlinkSync(tempFile); } catch (e) { // 忽略清理错误 } if (code === 0) { resolve('语法检查通过,无错误。'); } else { resolve(`语法检查发现错误:\n${stderr || stdout}`); } }); child.on('error', (error: Error) => { try { fs.unlinkSync(tempFile); } catch (e) { // 忽略清理错误 } reject(error); }); }); } catch (error) { // 确保清理临时文件 try { fs.unlinkSync(tempFile); } catch (e) { // 忽略 } throw error; } } /** * 执行 iverilog 工具 * 直接执行 iverilog 命令 */ async function executeIverilog( args: IverilogArgs, context: ToolExecutorContext ): Promise { // 检查 iverilog 是否可用 const iverilogCheck = await checkIverilogAvailable(context.extensionPath); if (!iverilogCheck.available) { throw new Error(`iverilog 不可用: ${iverilogCheck.message}`); } // 获取工作目录 const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { throw new Error('没有打开的工作区'); } const projectPath = workspaceFolders[0].uri.fsPath; const workDir = args.workDir ? path.join(projectPath, args.workDir) : projectPath; // 解析参数 const iverilogPath = getIverilogPath(context.extensionPath); const cmdArgs = args.args.split(/\s+/).filter(a => a.length > 0); const { spawn } = require('child_process'); return new Promise((resolve, reject) => { const child = spawn(iverilogPath, cmdArgs, { cwd: workDir, env: { ...process.env, IVERILOG_ROOT: path.join(context.extensionPath, 'tools', 'iverilog') } }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data: Buffer) => { stdout += data.toString(); }); child.stderr.on('data', (data: Buffer) => { stderr += data.toString(); }); child.on('close', (code: number) => { const output = stderr || stdout || '(无输出)'; if (code === 0) { resolve(`执行成功\n${output}`); } else { resolve(`执行失败 (exit code: ${code})\n${output}`); } }); child.on('error', (error: Error) => { reject(error); }); }); } /** * 执行 simulation 工具 */ async function executeSimulation( args: SimulationArgs, context: ToolExecutorContext ): Promise { // 获取工作区路径 const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { throw new Error('请先打开一个工作区'); } const projectPath = workspaceFolders[0].uri.fsPath; // 检查是否有 dumpModules 参数(多 VCD 模式) if (args.dumpModules) { const modules = parseDumpModules(args.dumpModules); const vcdDir = args.vcdDir || 'vcd'; const result = await generateMultiVCD( projectPath, context.extensionPath, args.tbPath, modules, vcdDir ); if (result.success) { const vcdList = result.vcdFiles .map(f => `- ${f.moduleName}: ${f.success ? f.vcdPath : '失败 - ' + f.error}`) .join('\n'); return `${result.message}\n\nVCD 文件列表:\n${vcdList}${result.stdout ? '\n\n仿真输出:' + result.stdout : ''}`; } else { throw new Error(result.message); } } // 原有单 VCD 逻辑 const result = await generateVCD(projectPath, context.extensionPath); if (result.success) { let message = result.message; if (result.stdout) { message += `\n\n仿真输出:\n${result.stdout}`; } return message; } else { let errorMessage = result.message; if (result.stderr) { errorMessage += `\n\n错误输出:\n${result.stderr}`; } throw new Error(errorMessage); } } /** * 解析 dumpModules 参数 * 格式:name:path,name:path */ function parseDumpModules(dumpModules: string): DumpModule[] { return dumpModules.split(',').map(item => { const [name, modulePath] = item.trim().split(':'); return { name: name.trim(), path: modulePath.trim() }; }); } /** * 执行 waveform_summary 工具 * 解析 VCD 文件并返回波形摘要 */ async function executeWaveformSummary(args: WaveformSummaryArgs): Promise { const { vcdPath, signals, checkpoints } = args; // 获取工作区路径 const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { throw new Error('请先打开一个工作区'); } const workspacePath = workspaceFolders[0].uri.fsPath; // 解析 VCD 文件路径(支持相对路径) const absolutePath = path.isAbsolute(vcdPath) ? vcdPath : path.join(workspacePath, vcdPath); // 检查文件是否存在 if (!fs.existsSync(absolutePath)) { throw new Error(`VCD 文件不存在: ${vcdPath}`); } // 解析检查点时间 const checkpoint = checkpoints ? parseInt(checkpoints, 10) : undefined; // 调用 VCD 解析器 const result = analyzeVcdFile(absolutePath, signals, checkpoint); return result; } /** * 执行 knowledge_save 工具 * 保存知识图谱到 .iccoder/knowledge.json */ async function executeKnowledgeSave(args: KnowledgeSaveArgs): Promise { const workspaceFolder = getWorkspaceFolder(); if (!workspaceFolder) { throw new Error('请先打开一个工作区'); } const iccoderDirUri = vscode.Uri.joinPath(workspaceFolder.uri, '.iccoder'); const knowledgeUri = vscode.Uri.joinPath(iccoderDirUri, 'knowledge.json'); // 确保 .iccoder 目录存在(兼容远程/虚拟工作区) await vscode.workspace.fs.createDirectory(iccoderDirUri); // 写入知识图谱(UTF-8) await vscode.workspace.fs.writeFile(knowledgeUri, Buffer.from(args.data || '', 'utf-8')); return `知识图谱已保存: .iccoder/knowledge.json`; } /** * 执行 knowledge_load 工具 * 从 .iccoder/knowledge.json 加载知识图谱 */ async function executeKnowledgeLoad(): Promise { const workspaceFolder = getWorkspaceFolder(); if (!workspaceFolder) { throw new Error('请先打开一个工作区'); } const knowledgeUri = vscode.Uri.joinPath(workspaceFolder.uri, '.iccoder', 'knowledge.json'); try { const bytes = await vscode.workspace.fs.readFile(knowledgeUri); const content = Buffer.from(bytes).toString('utf-8'); return content; } catch (error) { // 文件不存在:返回空图谱 if (error instanceof vscode.FileSystemError && error.code === 'FileNotFound') { // 与后端 KnowledgeGraph 结构保持一致(nodes/edges + nodeClass 多态字段) return JSON.stringify({ taskId: '', version: 1, module: null, nodes: [], edges: [] }); } throw error; } } function getWorkspaceFolder(): vscode.WorkspaceFolder | undefined { const folders = vscode.workspace.workspaceFolders; if (!folders || folders.length === 0) { return undefined; } const activeUri = vscode.window.activeTextEditor?.document?.uri; const activeFolder = activeUri ? vscode.workspace.getWorkspaceFolder(activeUri) : undefined; return activeFolder ?? folders[0]; } /** * 获取 iverilog 路径 */ function getIverilogPath(extensionPath: string): string { const platform = process.platform; if (platform === 'win32') { return path.join(extensionPath, 'tools', 'iverilog', 'bin', 'iverilog.exe'); } else { return path.join(extensionPath, 'tools', 'iverilog', 'bin', 'iverilog'); } } /** * 创建工具执行器上下文 */ export function createToolExecutorContext(extensionPath: string): ToolExecutorContext { const workspaceFolders = vscode.workspace.workspaceFolders; const workspacePath = workspaceFolders?.[0]?.uri.fsPath || ''; return { extensionPath, workspacePath }; }