Files
IC-Coder-Plugin/src/services/toolExecutor.ts
XiaoFeng 44bbcde5cf feat: 知识图谱工具支持 + 智能体事件处理
- dialogService: 添加智能体 SSE 事件处理
- toolExecutor: 添加 knowledge_save/knowledge_load 工具
- messageArea: 添加智能体消息渲染支持
- 添加 CLAUDE.md 项目配置
2025-12-30 09:40:04 +08:00

382 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 工具执行器
* 接收后端的 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 } from '../utils/iverilogRunner';
import {
submitToolResult,
createSuccessResult,
createBusinessErrorResult,
createSystemErrorResult
} from './apiClient';
import type {
ToolCallRequest,
ToolName,
FileReadArgs,
FileWriteArgs,
FileDeleteArgs,
FileListArgs,
SyntaxCheckArgs,
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<void> {
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 'simulation':
resultText = await executeSimulation(args as unknown as SimulationArgs, context);
break;
case 'waveform_summary':
resultText = await executeWaveformSummary(args as unknown as WaveformSummaryArgs);
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<string> {
const content = await readFileContent(args.path);
return content;
}
/**
* 执行 file_write 工具
*/
async function executeFileWrite(args: FileWriteArgs): Promise<string> {
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<string> {
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<string> {
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<string> {
// 检查 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;
}
}
/**
* 执行 simulation 工具
*/
async function executeSimulation(
args: SimulationArgs,
context: ToolExecutorContext
): Promise<string> {
// 获取工作区路径
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
throw new Error('请先打开一个工作区');
}
const projectPath = workspaceFolders[0].uri.fsPath;
// 调用现有的 generateVCD 函数
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);
}
}
/**
* 执行 waveform_summary 工具
* TODO: 实现 VCD 波形分析
*/
async function executeWaveformSummary(args: WaveformSummaryArgs): Promise<string> {
// TODO: 使用 vcdrom/vcd-stream 解析 VCD 文件
// 目前返回一个占位响应
return `波形分析功能暂未实现。\n请求参数:\n- VCD文件: ${args.vcdPath}\n- 信号: ${args.signals}\n- 检查点: ${args.checkpoints || '无'}`;
}
/**
* 执行 knowledge_save 工具
* 保存知识图谱到 .iccoder/knowledge.json
*/
async function executeKnowledgeSave(args: KnowledgeSaveArgs): Promise<string> {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
throw new Error('请先打开一个工作区');
}
const workspacePath = workspaceFolders[0].uri.fsPath;
const iccoderDir = path.join(workspacePath, '.iccoder');
const knowledgePath = path.join(iccoderDir, 'knowledge.json');
// 确保 .iccoder 目录存在
if (!fs.existsSync(iccoderDir)) {
fs.mkdirSync(iccoderDir, { recursive: true });
}
// 写入知识图谱
fs.writeFileSync(knowledgePath, args.data, 'utf-8');
return `知识图谱已保存: .iccoder/knowledge.json`;
}
/**
* 执行 knowledge_load 工具
* 从 .iccoder/knowledge.json 加载知识图谱
*/
async function executeKnowledgeLoad(): Promise<string> {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
throw new Error('请先打开一个工作区');
}
const workspacePath = workspaceFolders[0].uri.fsPath;
const knowledgePath = path.join(workspacePath, '.iccoder', 'knowledge.json');
// 如果文件不存在,返回空图谱
if (!fs.existsSync(knowledgePath)) {
return JSON.stringify({ directed: true, nodes: [], links: [] });
}
const content = fs.readFileSync(knowledgePath, 'utf-8');
return content;
}
/**
* 获取 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
};
}