feat: 添加 LSP 代码诊断功能

- 实现 LSP 客户端,支持与语言服务器通信
- 支持多种语言: TypeScript, Python, Go, Rust, C/C++ 等
- write_file/edit_file 工具集成 LSP 诊断,写入后自动检查代码错误
- 添加 CLI 命令管理语言服务器 (ai-assist lsp list/install/info)
- 智能等待机制:首次启动 LSP 等待 2s,后续仅需 300ms
- 将 read_file/write_file/edit_file 设为核心工具,确保文件操作使用正确的工具
- 更新系统提示词,引导 AI 使用文件工具而非 bash 命令
This commit is contained in:
2025-12-11 00:01:58 +08:00
parent 1e0ecc2de7
commit 929f6f7850
12 changed files with 1430 additions and 5 deletions
+21 -2
View File
@@ -4,6 +4,7 @@ import type { ToolResult } from '../../types/index.js';
import type { ToolWithMetadata } from '../types.js';
import { loadDescription } from '../load_description.js';
import { getPermissionManager } from '../../permission/index.js';
import { touchFile, getFormattedFileDiagnostics, isLanguageSupported } from '../../lsp/index.js';
export const editFileTool: ToolWithMetadata = {
name: 'edit_file',
@@ -13,7 +14,7 @@ export const editFileTool: ToolWithMetadata = {
category: 'filesystem',
description: '编辑文件内容(查找替换)',
keywords: ['edit', 'file', 'replace', 'modify', 'change', 'update', '编辑', '文件', '替换', '修改', '更新'],
deferLoading: true,
deferLoading: false, // 核心工具,始终可用
},
parameters: {
path: {
@@ -87,9 +88,27 @@ export const editFileTool: ToolWithMetadata = {
const newContent = content.replace(oldString, newString);
await fs.writeFile(absolutePath, newContent, 'utf-8');
let output = `文件已编辑: ${absolutePath}`;
// 如果支持 LSP,通知语言服务器并获取诊断
if (isLanguageSupported(absolutePath)) {
try {
const isFirstStart = await touchFile(absolutePath, false);
// 首次启动需要更长时间,后续只需短暂等待
const waitTime = isFirstStart ? 2000 : 300;
await new Promise(resolve => setTimeout(resolve, waitTime));
const diagnostics = await getFormattedFileDiagnostics(absolutePath);
if (diagnostics) {
output += `\n\n⚠️ 代码检查发现问题,请修复:${diagnostics}`;
}
} catch {
// LSP 错误不影响主流程
}
}
return {
success: true,
output: `文件已编辑: ${absolutePath}`,
output,
};
} catch (error) {
return {
+1 -1
View File
@@ -13,7 +13,7 @@ export const readFileTool: ToolWithMetadata = {
category: 'filesystem',
description: '读取文件内容',
keywords: ['read', 'file', 'content', 'cat', 'view', 'open', '读取', '文件', '内容', '查看', '打开'],
deferLoading: true,
deferLoading: false, // 核心工具,始终可用
},
parameters: {
path: {
+22 -2
View File
@@ -4,6 +4,7 @@ import type { ToolResult } from '../../types/index.js';
import type { ToolWithMetadata } from '../types.js';
import { loadDescription } from '../load_description.js';
import { getPermissionManager } from '../../permission/index.js';
import { touchFile, getFormattedFileDiagnostics, isLanguageSupported } from '../../lsp/index.js';
export const writeFileTool: ToolWithMetadata = {
name: 'write_file',
@@ -13,7 +14,7 @@ export const writeFileTool: ToolWithMetadata = {
category: 'filesystem',
description: '写入文件内容',
keywords: ['write', 'file', 'save', 'create', '写入', '文件', '保存', '创建', '新建'],
deferLoading: true,
deferLoading: false, // 核心工具,始终可用
},
parameters: {
path: {
@@ -61,9 +62,28 @@ export const writeFileTool: ToolWithMetadata = {
try {
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
await fs.writeFile(absolutePath, content, 'utf-8');
let output = `文件已写入: ${absolutePath}`;
// 如果支持 LSP,通知语言服务器并获取诊断
if (isLanguageSupported(absolutePath)) {
try {
const isFirstStart = await touchFile(absolutePath, true);
// 首次启动需要更长时间,后续只需短暂等待
const waitTime = isFirstStart ? 2000 : 300;
await new Promise(resolve => setTimeout(resolve, waitTime));
const diagnostics = await getFormattedFileDiagnostics(absolutePath);
if (diagnostics) {
output += `\n\n⚠️ 代码检查发现问题,请修复:${diagnostics}`;
}
} catch {
// LSP 错误不影响主流程
}
}
return {
success: true,
output: `文件已写入: ${absolutePath}`,
output,
};
} catch (error) {
return {