- 新增对话服务(src/services/dialogService.ts) - 封装完整的对话生命周期管理 - 集成 SSE 流式响应处理 - 支持对话创建、消息发送、对话中止 - 提供统一的事件回调接口 - 新增工具执行器(src/services/toolExecutor.ts) - 实现前端工具调用框架 - 支持 readFile、writeFile、listFiles、executeCommand 等工具 - 提供工具执行结果的标准化返回 - 集成 VSCode API 进行文件和终端操作 - 新增用户交互处理(src/services/userInteraction.ts) - 实现 AI 向用户提问功能(AskUser) - 支持 input、confirm、quickPick 等交互类型 - 使用 VSCode 原生 UI 组件展示问题 - 提供答案收集和提交机制
273 lines
7.4 KiB
TypeScript
273 lines
7.4 KiB
TypeScript
/**
|
||
* 工具执行器
|
||
* 接收后端的 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,
|
||
FileListArgs,
|
||
SyntaxCheckArgs,
|
||
SimulationArgs,
|
||
WaveformSummaryArgs
|
||
} 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_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;
|
||
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);
|
||
return `文件已写入: ${args.path}`;
|
||
}
|
||
|
||
/**
|
||
* 执行 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 || '无'}`;
|
||
}
|
||
|
||
/**
|
||
* 获取 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
|
||
};
|
||
}
|