@saber2pr/ai-agent 0.0.46 → 0.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/core/agent-graph.d.ts +2 -2
- package/lib/core/agent-graph.js +16 -7
- package/lib/model/AgentGraphModel.d.ts +9 -2
- package/lib/model/AgentGraphModel.js +28 -6
- package/lib/tools/filesystem/index.js +18 -2
- package/lib/tools/filesystem/lib.d.ts +1 -0
- package/lib/tools/filesystem/lib.js +34 -2
- package/lib/tools/filesystem/path-utils.js +7 -7
- package/lib/tools/filesystem/path-validation.js +1 -1
- package/lib/utils/kit.d.ts +1 -0
- package/lib/utils/kit.js +5 -0
- package/package.json +1 -1
|
@@ -43,12 +43,12 @@ export default class McpGraphAgent<T extends AgentGraphModel = any> {
|
|
|
43
43
|
private loadMcpConfigs;
|
|
44
44
|
private initMcpTools;
|
|
45
45
|
private prepareTools;
|
|
46
|
-
ensureInitialized(): Promise<
|
|
46
|
+
ensureInitialized(): Promise<T>;
|
|
47
47
|
private closeMcpClients;
|
|
48
48
|
private showLoading;
|
|
49
49
|
private startLoading;
|
|
50
50
|
private stopLoading;
|
|
51
|
-
getModel
|
|
51
|
+
private getModel;
|
|
52
52
|
private askForConfig;
|
|
53
53
|
chat(query?: string): Promise<void>;
|
|
54
54
|
/**
|
package/lib/core/agent-graph.js
CHANGED
|
@@ -180,17 +180,29 @@ class McpGraphAgent {
|
|
|
180
180
|
const allToolInfos = [...builtinToolInfos, ...(this.options.tools || []), ...mcpToolInfos];
|
|
181
181
|
this.langchainTools = allToolInfos.map(t => (0, convertToLangChainTool_1.convertToLangChainTool)(t));
|
|
182
182
|
this.toolNode = new prebuilt_1.ToolNode(this.langchainTools);
|
|
183
|
+
return {
|
|
184
|
+
builtinToolInfos,
|
|
185
|
+
mcpToolInfos,
|
|
186
|
+
tools: this.options.tools,
|
|
187
|
+
langchainTools: this.langchainTools,
|
|
188
|
+
};
|
|
183
189
|
}
|
|
184
190
|
// ✅ 修改:初始化逻辑
|
|
185
191
|
async ensureInitialized() {
|
|
186
|
-
if (this.model && this.langchainTools.length > 0)
|
|
187
|
-
return;
|
|
192
|
+
if (this.model && this.langchainTools.length > 0) {
|
|
193
|
+
return this.getModel();
|
|
194
|
+
}
|
|
195
|
+
;
|
|
188
196
|
// 1. 加载所有工具(含 MCP)
|
|
189
|
-
await this.prepareTools();
|
|
197
|
+
const toolsInfo = await this.prepareTools();
|
|
190
198
|
// 2. 初始化模型
|
|
191
|
-
await this.getModel();
|
|
199
|
+
const apiModel = await this.getModel();
|
|
200
|
+
if (toolsInfo.mcpToolInfos && apiModel.setMcpTools) {
|
|
201
|
+
apiModel.setMcpTools(toolsInfo.mcpToolInfos);
|
|
202
|
+
}
|
|
192
203
|
// 3. 打印工具状态
|
|
193
204
|
this.printLoadedTools();
|
|
205
|
+
return apiModel;
|
|
194
206
|
}
|
|
195
207
|
// ✅ 新增:关闭连接
|
|
196
208
|
async closeMcpClients() {
|
|
@@ -268,7 +280,6 @@ class McpGraphAgent {
|
|
|
268
280
|
async chat(query = '开始代码审计') {
|
|
269
281
|
try {
|
|
270
282
|
await this.ensureInitialized();
|
|
271
|
-
await this.getModel();
|
|
272
283
|
const app = await this.createGraph();
|
|
273
284
|
const graphStream = await app.stream({
|
|
274
285
|
messages: [new messages_1.HumanMessage(query)],
|
|
@@ -298,7 +309,6 @@ class McpGraphAgent {
|
|
|
298
309
|
this.streamEnabled = true;
|
|
299
310
|
try {
|
|
300
311
|
await this.ensureInitialized();
|
|
301
|
-
await this.getModel();
|
|
302
312
|
const app = await this.createGraph();
|
|
303
313
|
const graphStream = await app.stream({
|
|
304
314
|
messages: [new messages_1.HumanMessage(query)],
|
|
@@ -322,7 +332,6 @@ class McpGraphAgent {
|
|
|
322
332
|
}
|
|
323
333
|
async start() {
|
|
324
334
|
await this.ensureInitialized();
|
|
325
|
-
await this.getModel();
|
|
326
335
|
const app = await this.createGraph();
|
|
327
336
|
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
328
337
|
rl.on('SIGINT', () => {
|
|
@@ -11,9 +11,16 @@ export interface AgentGraphLLMResponse {
|
|
|
11
11
|
export type StreamChunkCallback = (chunk: string) => void;
|
|
12
12
|
export declare abstract class AgentGraphModel extends BaseChatModel {
|
|
13
13
|
protected boundTools?: any[];
|
|
14
|
+
private mcpEnabled?;
|
|
15
|
+
private mcpTools?;
|
|
16
|
+
setMcpTools(tools: any[]): void;
|
|
17
|
+
setMcpEnabled(enabled?: boolean): void;
|
|
18
|
+
getMcpEnabled(): boolean;
|
|
19
|
+
getMcpTools(): any[];
|
|
20
|
+
private isMcpTool;
|
|
14
21
|
constructor(fields?: BaseChatModelParams);
|
|
15
22
|
bindTools(tools: any[]): any;
|
|
16
|
-
abstract callApi(prompt: string): Promise<AgentGraphLLMResponse>;
|
|
23
|
+
abstract callApi(prompt: string, lastMsg: BaseMessage): Promise<AgentGraphLLMResponse>;
|
|
17
24
|
/**
|
|
18
25
|
* 流式调用 API,子类可覆盖以实现真正的 SSE 流式传输。
|
|
19
26
|
* 默认回退到 callApi 非流式调用。
|
|
@@ -21,7 +28,7 @@ export declare abstract class AgentGraphModel extends BaseChatModel {
|
|
|
21
28
|
* @param onChunk 每收到一段文本时的回调
|
|
22
29
|
* @returns 完整的响应结果
|
|
23
30
|
*/
|
|
24
|
-
callApiStream(prompt: string, onChunk: StreamChunkCallback): Promise<AgentGraphLLMResponse>;
|
|
31
|
+
callApiStream(prompt: string, lastMsg: BaseMessage, onChunk: StreamChunkCallback): Promise<AgentGraphLLMResponse>;
|
|
25
32
|
_generate(messages: BaseMessage[]): Promise<ChatResult>;
|
|
26
33
|
private parseToolCalls;
|
|
27
34
|
/**
|
|
@@ -5,9 +5,28 @@ const chat_models_1 = require("@langchain/core/language_models/chat_models");
|
|
|
5
5
|
const messages_1 = require("@langchain/core/messages");
|
|
6
6
|
const function_calling_1 = require("@langchain/core/utils/function_calling");
|
|
7
7
|
const cleanToolDefinition_1 = require("../utils/cleanToolDefinition");
|
|
8
|
+
const kit_1 = require("../utils/kit");
|
|
8
9
|
class AgentGraphModel extends chat_models_1.BaseChatModel {
|
|
10
|
+
setMcpTools(tools) {
|
|
11
|
+
this.mcpTools = tools;
|
|
12
|
+
}
|
|
13
|
+
setMcpEnabled(enabled = true) {
|
|
14
|
+
this.mcpEnabled = enabled;
|
|
15
|
+
}
|
|
16
|
+
getMcpEnabled() {
|
|
17
|
+
return this.mcpEnabled;
|
|
18
|
+
}
|
|
19
|
+
getMcpTools() {
|
|
20
|
+
return this.mcpTools;
|
|
21
|
+
}
|
|
22
|
+
isMcpTool(tool) {
|
|
23
|
+
const mcpTools = (0, kit_1.getArray)(this.mcpTools);
|
|
24
|
+
return mcpTools.some(t => { var _a, _b; return ((_a = t === null || t === void 0 ? void 0 : t.function) === null || _a === void 0 ? void 0 : _a.name) === ((_b = tool === null || tool === void 0 ? void 0 : tool.function) === null || _b === void 0 ? void 0 : _b.name); });
|
|
25
|
+
}
|
|
9
26
|
constructor(fields) {
|
|
10
27
|
super(fields || {});
|
|
28
|
+
this.mcpEnabled = true;
|
|
29
|
+
this.mcpTools = [];
|
|
11
30
|
}
|
|
12
31
|
bindTools(tools) {
|
|
13
32
|
this.boundTools = tools.map(t => (0, function_calling_1.convertToOpenAITool)(t));
|
|
@@ -20,14 +39,15 @@ class AgentGraphModel extends chat_models_1.BaseChatModel {
|
|
|
20
39
|
* @param onChunk 每收到一段文本时的回调
|
|
21
40
|
* @returns 完整的响应结果
|
|
22
41
|
*/
|
|
23
|
-
async callApiStream(prompt, onChunk) {
|
|
24
|
-
const result = await this.callApi(prompt);
|
|
42
|
+
async callApiStream(prompt, lastMsg, onChunk) {
|
|
43
|
+
const result = await this.callApi(prompt, lastMsg);
|
|
25
44
|
onChunk(result.text);
|
|
26
45
|
return result;
|
|
27
46
|
}
|
|
28
47
|
async _generate(messages) {
|
|
29
48
|
const fullPrompt = this.serializeMessages(messages);
|
|
30
|
-
const
|
|
49
|
+
const lastMsg = messages[messages.length - 1];
|
|
50
|
+
const response = await this.callApi(fullPrompt, lastMsg);
|
|
31
51
|
let { text, reasoning } = response;
|
|
32
52
|
// 1. 处理思考内容
|
|
33
53
|
if (!reasoning && text.includes('<think>')) {
|
|
@@ -111,7 +131,8 @@ class AgentGraphModel extends chat_models_1.BaseChatModel {
|
|
|
111
131
|
*/
|
|
112
132
|
async streamGenerate(messages, onChunk) {
|
|
113
133
|
const fullPrompt = this.serializeMessages(messages);
|
|
114
|
-
const
|
|
134
|
+
const lastMsg = messages[messages.length - 1];
|
|
135
|
+
const response = await this.callApiStream(fullPrompt, lastMsg, onChunk);
|
|
115
136
|
let { text, reasoning } = response;
|
|
116
137
|
// 1. 处理思考内容
|
|
117
138
|
if (!reasoning && text.includes('<think>')) {
|
|
@@ -152,8 +173,9 @@ class AgentGraphModel extends chat_models_1.BaseChatModel {
|
|
|
152
173
|
const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content, null, 2);
|
|
153
174
|
return `${m._getType().toUpperCase()}: ${content}`;
|
|
154
175
|
};
|
|
155
|
-
const
|
|
156
|
-
|
|
176
|
+
const tools = this.getMcpEnabled() ? (0, kit_1.getArray)(this.boundTools) : (0, kit_1.getArray)(this.boundTools).filter(tool => !this.isMcpTool(tool));
|
|
177
|
+
const toolsContext = tools.length
|
|
178
|
+
? `\n[Tools]\n${JSON.stringify(tools.map(cleanToolDefinition_1.cleanToolDefinition), null, 2)}`
|
|
157
179
|
: '';
|
|
158
180
|
return `
|
|
159
181
|
${format(systemMsg)}
|
|
@@ -10,6 +10,16 @@ const path_1 = __importDefault(require("path"));
|
|
|
10
10
|
const zod_1 = require("zod");
|
|
11
11
|
const createTool_1 = require("../../utils/createTool");
|
|
12
12
|
const lib_1 = require("./lib");
|
|
13
|
+
const DEFAULT_IGNORE = [
|
|
14
|
+
'node_modules/**',
|
|
15
|
+
'.git/**',
|
|
16
|
+
'dist/**',
|
|
17
|
+
'build/**',
|
|
18
|
+
'.next/**',
|
|
19
|
+
'out/**',
|
|
20
|
+
'*.log',
|
|
21
|
+
'.DS_Store'
|
|
22
|
+
];
|
|
13
23
|
// Schema definitions
|
|
14
24
|
const ReadTextFileArgsSchema = zod_1.z.object({
|
|
15
25
|
path: zod_1.z.string(),
|
|
@@ -215,6 +225,8 @@ const getFilesystemTools = (targetDir) => {
|
|
|
215
225
|
'默认仅展示 2 层深度以节省 Token。如果需要看更深层级,请调大 depth 参数。',
|
|
216
226
|
parameters: DirectoryTreeArgsSchema,
|
|
217
227
|
handler: async (args) => {
|
|
228
|
+
// 在 directory_tree 的 handler 内部
|
|
229
|
+
const combinedExcludes = [...DEFAULT_IGNORE, ...(args.excludePatterns || [])];
|
|
218
230
|
const rootPath = args.path;
|
|
219
231
|
async function buildTree(currentPath, currentDepth, maxDepth, excludePatterns = []) {
|
|
220
232
|
if (currentDepth > maxDepth)
|
|
@@ -239,7 +251,7 @@ const getFilesystemTools = (targetDir) => {
|
|
|
239
251
|
}
|
|
240
252
|
return result;
|
|
241
253
|
}
|
|
242
|
-
const treeData = await buildTree(rootPath, 1, args.depth,
|
|
254
|
+
const treeData = await buildTree(rootPath, 1, args.depth, combinedExcludes);
|
|
243
255
|
return JSON.stringify(treeData, null, 2);
|
|
244
256
|
},
|
|
245
257
|
});
|
|
@@ -265,9 +277,10 @@ const getFilesystemTools = (targetDir) => {
|
|
|
265
277
|
'Used only for filename search',
|
|
266
278
|
parameters: SearchFilesArgsSchema,
|
|
267
279
|
handler: async (args) => {
|
|
280
|
+
const combinedExcludes = [...DEFAULT_IGNORE, ...(args.excludePatterns || [])];
|
|
268
281
|
const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
|
|
269
282
|
const results = await (0, lib_1.searchFilesWithValidation)(targetDir, validPath, args.pattern, [targetDir], {
|
|
270
|
-
excludePatterns:
|
|
283
|
+
excludePatterns: combinedExcludes,
|
|
271
284
|
});
|
|
272
285
|
const text = results.length > 0 ? results.join('\n') : 'No matches found';
|
|
273
286
|
return text;
|
|
@@ -315,6 +328,9 @@ const getFilesystemTools = (targetDir) => {
|
|
|
315
328
|
const stats = await promises_1.default.stat(filePath);
|
|
316
329
|
if (!stats.isFile())
|
|
317
330
|
return;
|
|
331
|
+
// 在 grep_search 扫描文件时增加判断
|
|
332
|
+
if ((0, lib_1.isBinaryOrIrrelevant)(path_1.default.extname(filePath)))
|
|
333
|
+
return;
|
|
318
334
|
const content = await (0, lib_1.readFileContent)(filePath);
|
|
319
335
|
if (content.includes(args.query)) {
|
|
320
336
|
const relativePath = path_1.default.relative(targetDir, filePath);
|
|
@@ -31,4 +31,5 @@ export declare function applyFileEdits(filePath: string, edits: FileEdit[], dryR
|
|
|
31
31
|
export declare function tailFile(filePath: string, numLines: number): Promise<string>;
|
|
32
32
|
export declare function headFile(filePath: string, numLines: number): Promise<string>;
|
|
33
33
|
export declare function searchFilesWithValidation(cwd: string, rootPath: string, pattern: string, allowedDirectories: string[], options?: SearchOptions): Promise<string[]>;
|
|
34
|
+
export declare const isBinaryOrIrrelevant: (filePath: string) => boolean;
|
|
34
35
|
export {};
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isBinaryOrIrrelevant = void 0;
|
|
6
7
|
exports.setAllowedDirectories = setAllowedDirectories;
|
|
7
8
|
exports.getAllowedDirectories = getAllowedDirectories;
|
|
8
9
|
exports.formatSize = formatSize;
|
|
@@ -115,7 +116,7 @@ async function writeFileContent(filePath, content) {
|
|
|
115
116
|
try {
|
|
116
117
|
// Security: 'wx' flag ensures exclusive creation - fails if file/symlink exists,
|
|
117
118
|
// preventing writes through pre-existing symlinks
|
|
118
|
-
await promises_1.default.writeFile(filePath, content, { encoding:
|
|
119
|
+
await promises_1.default.writeFile(filePath, content, { encoding: 'utf-8', flag: 'wx' });
|
|
119
120
|
}
|
|
120
121
|
catch (error) {
|
|
121
122
|
if (error.code === 'EEXIST') {
|
|
@@ -230,7 +231,7 @@ async function tailFile(filePath, numLines) {
|
|
|
230
231
|
try {
|
|
231
232
|
const lines = [];
|
|
232
233
|
let position = fileSize;
|
|
233
|
-
|
|
234
|
+
const chunk = Buffer.alloc(CHUNK_SIZE);
|
|
234
235
|
let linesFound = 0;
|
|
235
236
|
let remainingText = '';
|
|
236
237
|
// Read chunks from the end of the file until we have enough lines
|
|
@@ -328,3 +329,34 @@ async function searchFilesWithValidation(cwd, rootPath, pattern, allowedDirector
|
|
|
328
329
|
await search(rootPath);
|
|
329
330
|
return results;
|
|
330
331
|
}
|
|
332
|
+
const isBinaryOrIrrelevant = (filePath) => {
|
|
333
|
+
const ext = path_1.default.extname(filePath).toLowerCase();
|
|
334
|
+
// 1. 常见的二进制媒体/资源格式
|
|
335
|
+
const binaryExtensions = new Set([
|
|
336
|
+
// 图片
|
|
337
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.ico', '.tiff',
|
|
338
|
+
// 字体
|
|
339
|
+
'.ttf', '.otf', '.woff', '.woff2', '.eot',
|
|
340
|
+
// 压缩包
|
|
341
|
+
'.zip', '.tar', '.gz', '.7z', '.rar',
|
|
342
|
+
// 编译产物/执行文件
|
|
343
|
+
'.exe', '.dll', '.so', '.dylib', '.bin', '.obj', '.pyc', '.pyo', '.class',
|
|
344
|
+
// 音视频
|
|
345
|
+
'.mp3', '.mp4', '.mov', '.wav', '.flv', '.wmv',
|
|
346
|
+
// 文档 (虽然是文档,但通常是二进制格式)
|
|
347
|
+
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'
|
|
348
|
+
]);
|
|
349
|
+
if (binaryExtensions.has(ext))
|
|
350
|
+
return true;
|
|
351
|
+
// 2. 干扰性极强的非二进制文件 (Source Maps 等)
|
|
352
|
+
const noiseExtensions = new Set([
|
|
353
|
+
'.map', // 源代码映射文件,极长且无意义
|
|
354
|
+
'.lock', // package-lock.json, yarn.lock 等锁定文件 (可选,如果 AI 不需要分析版本)
|
|
355
|
+
'.pyc',
|
|
356
|
+
'.DS_Store'
|
|
357
|
+
]);
|
|
358
|
+
if (noiseExtensions.has(ext))
|
|
359
|
+
return true;
|
|
360
|
+
return false;
|
|
361
|
+
};
|
|
362
|
+
exports.isBinaryOrIrrelevant = isBinaryOrIrrelevant;
|
|
@@ -45,13 +45,13 @@ function normalizePath(p) {
|
|
|
45
45
|
// Check if this is a Unix path that should not be converted
|
|
46
46
|
// WSL paths (/mnt/) should ALWAYS be preserved as they work correctly in WSL with Node.js fs
|
|
47
47
|
// Regular Unix paths should also be preserved
|
|
48
|
-
const isUnixPath = p.startsWith('/') &&
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
const isUnixPath = p.startsWith('/') &&
|
|
49
|
+
// Always preserve WSL paths (/mnt/c/, /mnt/d/, etc.)
|
|
50
|
+
(p.match(/^\/mnt\/[a-z]\//i) ||
|
|
51
|
+
// On non-Windows platforms, treat all absolute paths as Unix paths
|
|
52
|
+
process.platform !== 'win32' ||
|
|
53
|
+
// On Windows, preserve Unix paths that aren't Unix-style Windows paths (/c/, /d/, etc.)
|
|
54
|
+
(process.platform === 'win32' && !p.match(/^\/[a-zA-Z]\//)));
|
|
55
55
|
if (isUnixPath) {
|
|
56
56
|
// For Unix paths, just normalize without converting to Windows format
|
|
57
57
|
// Replace double slashes with single slashes and remove trailing slashes
|
|
@@ -74,7 +74,7 @@ function isPathWithinAllowedDirectories(absolutePath, allowedDirectories) {
|
|
|
74
74
|
// Ensure both paths are on the same drive
|
|
75
75
|
const dirDrive = normalizedDir.charAt(0).toLowerCase();
|
|
76
76
|
const pathDrive = normalizedPath.charAt(0).toLowerCase();
|
|
77
|
-
return pathDrive === dirDrive && normalizedPath.startsWith(normalizedDir.replace(/\\?$/, '\\'));
|
|
77
|
+
return (pathDrive === dirDrive && normalizedPath.startsWith(normalizedDir.replace(/\\?$/, '\\')));
|
|
78
78
|
}
|
|
79
79
|
return normalizedPath.startsWith(normalizedDir + path_1.default.sep);
|
|
80
80
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getArray: <T = any>(array: T[]) => T[];
|
package/lib/utils/kit.js
ADDED