feat: 实现核心服务层
- 新增对话服务(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 组件展示问题 - 提供答案收集和提交机制
This commit is contained in:
272
src/services/toolExecutor.ts
Normal file
272
src/services/toolExecutor.ts
Normal file
@ -0,0 +1,272 @@
|
||||
/**
|
||||
* 工具执行器
|
||||
* 接收后端的 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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user