@saber2pr/ai-agent 0.0.64 → 0.0.66
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 +4 -4
- package/lib/core/agent-graph.js +101 -87
- package/lib/tools/builtin.d.ts +1 -1
- package/lib/tools/builtin.js +3 -1
- package/lib/tools/loader/batch_run_tools.d.ts +2 -0
- package/lib/tools/loader/batch_run_tools.js +56 -0
- package/lib/tools/loader/get_all_tools_schema.js +18 -7
- package/lib/types/type.d.ts +6 -4
- package/lib/utils/convertToLangChainTool.d.ts +2 -1
- package/lib/utils/createTool.d.ts +3 -2
- package/lib/utils/createTool.js +2 -2
- package/lib/utils/formatSchema.js +34 -10
- package/lib/utils/generateToolMarkdown.d.ts +0 -4
- package/lib/utils/generateToolMarkdown.js +30 -9
- package/lib/utils/getSystemPromptTemplate.js +1 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AIMessage, BaseMessage } from
|
|
2
|
-
import { GraphAgentOptions } from
|
|
3
|
-
import { AgentGraphModel } from
|
|
1
|
+
import { AIMessage, BaseMessage } from "@langchain/core/messages";
|
|
2
|
+
import { GraphAgentOptions } from "../types/type";
|
|
3
|
+
import { AgentGraphModel } from "../model/AgentGraphModel";
|
|
4
4
|
interface TokenUsage {
|
|
5
5
|
total: number;
|
|
6
6
|
}
|
|
@@ -30,7 +30,7 @@ export default class McpGraphAgent<T extends AgentGraphModel = any> {
|
|
|
30
30
|
* 设置外部流式输出回调(如 VS Code Webview)。
|
|
31
31
|
* 设置后,callModel 的流式输出将通过回调发送而非写入 stdout。
|
|
32
32
|
*/
|
|
33
|
-
setStreamOutput(callback: (chunk: string, type:
|
|
33
|
+
setStreamOutput(callback: (chunk: string, type: "think" | "text") => void): void;
|
|
34
34
|
/**
|
|
35
35
|
* 清除外部流式输出回调,恢复默认的 stdout 输出。
|
|
36
36
|
*/
|
package/lib/core/agent-graph.js
CHANGED
|
@@ -76,11 +76,11 @@ class McpGraphAgent {
|
|
|
76
76
|
const cleanup = async () => {
|
|
77
77
|
this.stopLoading();
|
|
78
78
|
await this.closeMcpClients(); // 清理 MCP 连接
|
|
79
|
-
process.stdout.write(
|
|
79
|
+
process.stdout.write("\u001B[?25h");
|
|
80
80
|
process.exit(0);
|
|
81
81
|
};
|
|
82
|
-
process.on(
|
|
83
|
-
process.on(
|
|
82
|
+
process.on("SIGINT", cleanup);
|
|
83
|
+
process.on("SIGTERM", cleanup);
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
86
|
* 设置外部流式输出回调(如 VS Code Webview)。
|
|
@@ -96,7 +96,7 @@ class McpGraphAgent {
|
|
|
96
96
|
this.streamOutputCallback = null;
|
|
97
97
|
}
|
|
98
98
|
printLoadedTools() {
|
|
99
|
-
console.log(
|
|
99
|
+
console.log("\n🛠️ [Graph] 正在加载工具节点...");
|
|
100
100
|
this.langchainTools.forEach((tool) => {
|
|
101
101
|
// 工具名称
|
|
102
102
|
console.log(`\n🧰 工具名: ${tool.name}`);
|
|
@@ -120,12 +120,12 @@ class McpGraphAgent {
|
|
|
120
120
|
loadMcpConfigs() {
|
|
121
121
|
const combined = { mcpServers: {} };
|
|
122
122
|
const paths = [
|
|
123
|
-
path_1.default.join(os_1.default.homedir(),
|
|
124
|
-
path_1.default.join(os_1.default.homedir(),
|
|
123
|
+
path_1.default.join(os_1.default.homedir(), ".cursor", "mcp.json"),
|
|
124
|
+
path_1.default.join(os_1.default.homedir(), ".vscode", "mcp.json"),
|
|
125
125
|
];
|
|
126
|
-
paths.forEach(p => {
|
|
126
|
+
paths.forEach((p) => {
|
|
127
127
|
if (fs_1.default.existsSync(p)) {
|
|
128
|
-
const content = JSON.parse(fs_1.default.readFileSync(p,
|
|
128
|
+
const content = JSON.parse(fs_1.default.readFileSync(p, "utf-8"));
|
|
129
129
|
Object.assign(combined.mcpServers, content.mcpServers);
|
|
130
130
|
}
|
|
131
131
|
});
|
|
@@ -142,13 +142,13 @@ class McpGraphAgent {
|
|
|
142
142
|
args: config.args,
|
|
143
143
|
env: { ...process.env, ...(config.env || {}) },
|
|
144
144
|
});
|
|
145
|
-
const client = new index_js_1.Client({ name:
|
|
145
|
+
const client = new index_js_1.Client({ name: "mcp-graph-client", version: "1.0.0" }, { capabilities: {} });
|
|
146
146
|
await client.connect(transport);
|
|
147
147
|
this.mcpClients.push(client);
|
|
148
148
|
const { tools } = await client.listTools();
|
|
149
|
-
tools.forEach(tool => {
|
|
149
|
+
tools.forEach((tool) => {
|
|
150
150
|
mcpToolInfos.push({
|
|
151
|
-
type:
|
|
151
|
+
type: "function",
|
|
152
152
|
function: {
|
|
153
153
|
name: tool.name,
|
|
154
154
|
description: tool.description,
|
|
@@ -172,11 +172,23 @@ class McpGraphAgent {
|
|
|
172
172
|
return mcpToolInfos;
|
|
173
173
|
}
|
|
174
174
|
async prepareTools() {
|
|
175
|
-
const builtinToolInfos = (0, builtin_1.createDefaultBuiltinTools)({
|
|
175
|
+
const builtinToolInfos = (0, builtin_1.createDefaultBuiltinTools)({
|
|
176
|
+
options: this.options,
|
|
177
|
+
});
|
|
176
178
|
const mcpToolInfos = await this.initMcpTools();
|
|
177
179
|
// 合并内置、手动传入和 MCP 工具
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
let allToolInfos = [
|
|
181
|
+
...builtinToolInfos,
|
|
182
|
+
...(this.options.tools || []),
|
|
183
|
+
...mcpToolInfos,
|
|
184
|
+
];
|
|
185
|
+
if (this.options.filterTools) {
|
|
186
|
+
allToolInfos = allToolInfos.filter(this.options.filterTools);
|
|
187
|
+
}
|
|
188
|
+
this.langchainTools = allToolInfos.map((t) => (0, convertToLangChainTool_1.convertToLangChainTool)(t, {
|
|
189
|
+
allTools: allToolInfos,
|
|
190
|
+
agentOptions: this.options,
|
|
191
|
+
}));
|
|
180
192
|
this.toolNode = new prebuilt_1.ToolNode(this.langchainTools);
|
|
181
193
|
return {
|
|
182
194
|
builtinToolInfos,
|
|
@@ -190,7 +202,6 @@ class McpGraphAgent {
|
|
|
190
202
|
if (this.model && this.langchainTools.length > 0) {
|
|
191
203
|
return this.getModel();
|
|
192
204
|
}
|
|
193
|
-
;
|
|
194
205
|
// 1. 加载所有工具(含 MCP)
|
|
195
206
|
const toolsInfo = await this.prepareTools();
|
|
196
207
|
// 2. 初始化模型
|
|
@@ -213,17 +224,17 @@ class McpGraphAgent {
|
|
|
213
224
|
this.mcpClients = [];
|
|
214
225
|
}
|
|
215
226
|
showLoading(text) {
|
|
216
|
-
const chars = [
|
|
227
|
+
const chars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
217
228
|
let i = 0;
|
|
218
|
-
process.stdout.write(
|
|
229
|
+
process.stdout.write("\u001B[?25l");
|
|
219
230
|
const timer = setInterval(() => {
|
|
220
231
|
process.stdout.write(`\r\x1b[36m${chars[i]}\x1b[0m ${text}`);
|
|
221
232
|
i = (i + 1) % chars.length;
|
|
222
233
|
}, 80);
|
|
223
234
|
return () => {
|
|
224
235
|
clearInterval(timer);
|
|
225
|
-
process.stdout.write(
|
|
226
|
-
process.stdout.write(
|
|
236
|
+
process.stdout.write("\r\x1b[K");
|
|
237
|
+
process.stdout.write("\u001B[?25h");
|
|
227
238
|
};
|
|
228
239
|
}
|
|
229
240
|
startLoading(text) {
|
|
@@ -260,29 +271,33 @@ class McpGraphAgent {
|
|
|
260
271
|
let config = {};
|
|
261
272
|
if (fs_1.default.existsSync(config_1.CONFIG_FILE)) {
|
|
262
273
|
try {
|
|
263
|
-
config = JSON.parse(fs_1.default.readFileSync(config_1.CONFIG_FILE,
|
|
274
|
+
config = JSON.parse(fs_1.default.readFileSync(config_1.CONFIG_FILE, "utf-8"));
|
|
264
275
|
}
|
|
265
276
|
catch (e) { }
|
|
266
277
|
}
|
|
267
278
|
if (!config.baseURL || !config.apiKey) {
|
|
268
|
-
const rl = readline_1.default.createInterface({
|
|
269
|
-
|
|
279
|
+
const rl = readline_1.default.createInterface({
|
|
280
|
+
input: process.stdin,
|
|
281
|
+
output: process.stdout,
|
|
282
|
+
});
|
|
283
|
+
const question = (q) => new Promise((res) => rl.question(q, res));
|
|
270
284
|
config.baseURL = config.baseURL || (await question(`? API Base URL: `));
|
|
271
285
|
config.apiKey = config.apiKey || (await question(`? API Key: `));
|
|
272
|
-
config.model =
|
|
286
|
+
config.model =
|
|
287
|
+
config.model || (await question(`? Model Name: `)) || "gpt-4o";
|
|
273
288
|
fs_1.default.writeFileSync(config_1.CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
274
289
|
rl.close();
|
|
275
290
|
}
|
|
276
291
|
return config;
|
|
277
292
|
}
|
|
278
|
-
async chat(query =
|
|
293
|
+
async chat(query = "开始代码审计") {
|
|
279
294
|
try {
|
|
280
295
|
await this.ensureInitialized();
|
|
281
296
|
const app = await this.createGraph();
|
|
282
297
|
const graphStream = await app.stream({
|
|
283
298
|
messages: [new messages_1.HumanMessage(query)],
|
|
284
299
|
}, {
|
|
285
|
-
configurable: { thread_id:
|
|
300
|
+
configurable: { thread_id: "auto_worker" },
|
|
286
301
|
recursionLimit: this.recursionLimit,
|
|
287
302
|
debug: this.verbose,
|
|
288
303
|
});
|
|
@@ -290,7 +305,7 @@ class McpGraphAgent {
|
|
|
290
305
|
this.renderOutput(output, this.streamEnabled);
|
|
291
306
|
}
|
|
292
307
|
catch (error) {
|
|
293
|
-
console.error(
|
|
308
|
+
console.error("\n❌ Chat 过程中发生错误:", error);
|
|
294
309
|
}
|
|
295
310
|
finally {
|
|
296
311
|
await this.closeMcpClients();
|
|
@@ -300,7 +315,7 @@ class McpGraphAgent {
|
|
|
300
315
|
* 流式执行单次查询(编程式 API)。
|
|
301
316
|
* 无论 options.stream 是否开启,此方法始终以流式方式输出。
|
|
302
317
|
*/
|
|
303
|
-
async stream(query =
|
|
318
|
+
async stream(query = "开始代码审计") {
|
|
304
319
|
const prevStream = this.streamEnabled;
|
|
305
320
|
this.streamEnabled = true;
|
|
306
321
|
try {
|
|
@@ -309,7 +324,7 @@ class McpGraphAgent {
|
|
|
309
324
|
const graphStream = await app.stream({
|
|
310
325
|
messages: [new messages_1.HumanMessage(query)],
|
|
311
326
|
}, {
|
|
312
|
-
configurable: { thread_id:
|
|
327
|
+
configurable: { thread_id: "stream_worker" },
|
|
313
328
|
recursionLimit: this.recursionLimit,
|
|
314
329
|
debug: this.verbose,
|
|
315
330
|
});
|
|
@@ -317,7 +332,7 @@ class McpGraphAgent {
|
|
|
317
332
|
this.renderOutput(output, true);
|
|
318
333
|
}
|
|
319
334
|
catch (error) {
|
|
320
|
-
console.error(
|
|
335
|
+
console.error("\n❌ Stream 过程中发生错误:", error);
|
|
321
336
|
}
|
|
322
337
|
finally {
|
|
323
338
|
this.streamEnabled = prevStream;
|
|
@@ -327,21 +342,24 @@ class McpGraphAgent {
|
|
|
327
342
|
async start() {
|
|
328
343
|
await this.ensureInitialized();
|
|
329
344
|
const app = await this.createGraph();
|
|
330
|
-
const rl = readline_1.default.createInterface({
|
|
331
|
-
|
|
345
|
+
const rl = readline_1.default.createInterface({
|
|
346
|
+
input: process.stdin,
|
|
347
|
+
output: process.stdout,
|
|
348
|
+
});
|
|
349
|
+
rl.on("SIGINT", () => {
|
|
332
350
|
this.stopLoading();
|
|
333
351
|
rl.close();
|
|
334
|
-
process.stdout.write(
|
|
352
|
+
process.stdout.write("\u001B[?25h");
|
|
335
353
|
process.exit(0);
|
|
336
354
|
});
|
|
337
355
|
const ask = () => {
|
|
338
|
-
rl.question(
|
|
339
|
-
if (input.toLowerCase() ===
|
|
356
|
+
rl.question("> ", async (input) => {
|
|
357
|
+
if (input.toLowerCase() === "exit") {
|
|
340
358
|
rl.close();
|
|
341
359
|
return;
|
|
342
360
|
}
|
|
343
361
|
const graphStream = await app.stream({ messages: [new messages_1.HumanMessage(input)] }, {
|
|
344
|
-
configurable: { thread_id:
|
|
362
|
+
configurable: { thread_id: "session" },
|
|
345
363
|
recursionLimit: this.recursionLimit,
|
|
346
364
|
debug: this.verbose,
|
|
347
365
|
});
|
|
@@ -358,7 +376,9 @@ class McpGraphAgent {
|
|
|
358
376
|
// ✅ 打印工具执行结果(tools 节点的输出)
|
|
359
377
|
const toolsNode = output.tools;
|
|
360
378
|
if (toolsNode && toolsNode.messages) {
|
|
361
|
-
const toolMessages = Array.isArray(toolsNode.messages)
|
|
379
|
+
const toolMessages = Array.isArray(toolsNode.messages)
|
|
380
|
+
? toolsNode.messages
|
|
381
|
+
: [];
|
|
362
382
|
// 获取最近的 AI 消息以匹配 tool_call_id
|
|
363
383
|
const lastAiMsg = agentNode?.messages?.[agentNode.messages.length - 1];
|
|
364
384
|
const toolCallMap = new Map();
|
|
@@ -372,10 +392,12 @@ class McpGraphAgent {
|
|
|
372
392
|
// ToolMessage 有 tool_call_id 字段
|
|
373
393
|
const toolCallId = msg.tool_call_id || msg.id;
|
|
374
394
|
if (toolCallId) {
|
|
375
|
-
const toolName = toolCallMap.get(toolCallId) || msg.name ||
|
|
376
|
-
const content = typeof msg.content ===
|
|
395
|
+
const toolName = toolCallMap.get(toolCallId) || msg.name || "unknown";
|
|
396
|
+
const content = typeof msg.content === "string"
|
|
397
|
+
? msg.content
|
|
398
|
+
: JSON.stringify(msg.content);
|
|
377
399
|
// 如果内容太长,截断显示
|
|
378
|
-
const displayContent = content.length > 500 ? content.substring(0, 500) +
|
|
400
|
+
const displayContent = content.length > 500 ? content.substring(0, 500) + "..." : content;
|
|
379
401
|
console.log(`✅ [工具结果] ${toolName}: ${displayContent}`);
|
|
380
402
|
}
|
|
381
403
|
});
|
|
@@ -402,7 +424,7 @@ class McpGraphAgent {
|
|
|
402
424
|
}
|
|
403
425
|
// 4. 打印工具调用情况
|
|
404
426
|
if (msg.tool_calls?.length) {
|
|
405
|
-
msg.tool_calls.forEach(call => {
|
|
427
|
+
msg.tool_calls.forEach((call) => {
|
|
406
428
|
console.log(`🛠️ [调用工具]: ${call.name} 📦 参数: ${JSON.stringify(call.args)}`);
|
|
407
429
|
});
|
|
408
430
|
}
|
|
@@ -412,9 +434,9 @@ class McpGraphAgent {
|
|
|
412
434
|
const recentToolCalls = this.getRecentToolCalls(state.messages);
|
|
413
435
|
const recentToolCallsStr = recentToolCalls.length > 0
|
|
414
436
|
? `\n\n⚠️ 最近调用的工具(避免重复调用相同工具和参数):\n${recentToolCalls
|
|
415
|
-
.map(tc => ` - ${tc.name}(${JSON.stringify(tc.args)})`)
|
|
416
|
-
.join(
|
|
417
|
-
:
|
|
437
|
+
.map((tc) => ` - ${tc.name}(${JSON.stringify(tc.args)})`)
|
|
438
|
+
.join("\n")}`
|
|
439
|
+
: "";
|
|
418
440
|
// 1. 构建当前的系统提示词模板
|
|
419
441
|
const systemPromptTemplate = `
|
|
420
442
|
${(0, getSystemPromptTemplate_1.getSystemPromptTemplate)(this.targetDir)}
|
|
@@ -425,32 +447,32 @@ ${(0, getSystemPromptTemplate_1.getSystemPromptTemplate)(this.targetDir)}
|
|
|
425
447
|
// ✅ 检查 options 中的 alwaysSystem 参数 (默认为 true 或根据你的需求设置)
|
|
426
448
|
// 如果不希望每次都携带(即只在首轮携带),则过滤掉历史消息里的 SystemMessage
|
|
427
449
|
if (this.options.alwaysSystem === false) {
|
|
428
|
-
inputMessages = state.messages.filter(msg => msg._getType() !==
|
|
450
|
+
inputMessages = state.messages.filter((msg) => msg._getType() !== "system");
|
|
429
451
|
}
|
|
430
452
|
else {
|
|
431
453
|
// 默认模式:保持干净,由 PromptTemplate 重新生成最新的 System 状态
|
|
432
|
-
inputMessages = state.messages.filter(msg => msg._getType() !==
|
|
454
|
+
inputMessages = state.messages.filter((msg) => msg._getType() !== "system");
|
|
433
455
|
}
|
|
434
456
|
const prompt = prompts_1.ChatPromptTemplate.fromMessages([
|
|
435
|
-
[
|
|
436
|
-
new prompts_1.MessagesPlaceholder(
|
|
457
|
+
["system", systemPromptTemplate],
|
|
458
|
+
new prompts_1.MessagesPlaceholder("messages"),
|
|
437
459
|
]);
|
|
438
|
-
this.startLoading(
|
|
460
|
+
this.startLoading("AI 正在分析并思考中");
|
|
439
461
|
try {
|
|
440
462
|
const promptParams = {
|
|
441
463
|
messages: inputMessages,
|
|
442
464
|
recentToolCalls: recentToolCallsStr,
|
|
443
|
-
extraPrompt: this.options.extraSystemPrompt ||
|
|
465
|
+
extraPrompt: this.options.extraSystemPrompt || "",
|
|
444
466
|
};
|
|
445
467
|
if (this.streamEnabled) {
|
|
446
468
|
// ✅ 流式模式:通过 AgentGraphModel.streamGenerate 进行流式输出
|
|
447
469
|
const formattedMessages = await prompt.formatMessages(promptParams);
|
|
448
470
|
this.stopLoading();
|
|
449
471
|
// --- 流式 <think> 标签实时过滤 + 流式打印思考内容 ---
|
|
450
|
-
const THINK_OPEN =
|
|
451
|
-
const THINK_CLOSE =
|
|
472
|
+
const THINK_OPEN = "<think>";
|
|
473
|
+
const THINK_CLOSE = "</think>";
|
|
452
474
|
let inThink = false;
|
|
453
|
-
let textBuffer =
|
|
475
|
+
let textBuffer = "";
|
|
454
476
|
let aiHeaderPrinted = false;
|
|
455
477
|
let thinkHeaderPrinted = false;
|
|
456
478
|
const hasExternalHandler = !!this.streamOutputCallback;
|
|
@@ -458,11 +480,11 @@ ${(0, getSystemPromptTemplate_1.getSystemPromptTemplate)(this.targetDir)}
|
|
|
458
480
|
if (!text)
|
|
459
481
|
return;
|
|
460
482
|
if (hasExternalHandler) {
|
|
461
|
-
this.streamOutputCallback(text,
|
|
483
|
+
this.streamOutputCallback(text, "text");
|
|
462
484
|
}
|
|
463
485
|
else {
|
|
464
486
|
if (!aiHeaderPrinted) {
|
|
465
|
-
process.stdout.write(
|
|
487
|
+
process.stdout.write("🤖 [AI]: ");
|
|
466
488
|
aiHeaderPrinted = true;
|
|
467
489
|
}
|
|
468
490
|
process.stdout.write(text);
|
|
@@ -472,11 +494,11 @@ ${(0, getSystemPromptTemplate_1.getSystemPromptTemplate)(this.targetDir)}
|
|
|
472
494
|
if (!text)
|
|
473
495
|
return;
|
|
474
496
|
if (hasExternalHandler) {
|
|
475
|
-
this.streamOutputCallback(text,
|
|
497
|
+
this.streamOutputCallback(text, "think");
|
|
476
498
|
}
|
|
477
499
|
else {
|
|
478
500
|
if (!thinkHeaderPrinted) {
|
|
479
|
-
process.stdout.write(
|
|
501
|
+
process.stdout.write("\x1b[2m🧠 [思考]: ");
|
|
480
502
|
thinkHeaderPrinted = true;
|
|
481
503
|
}
|
|
482
504
|
process.stdout.write(text);
|
|
@@ -496,7 +518,7 @@ ${(0, getSystemPromptTemplate_1.getSystemPromptTemplate)(this.targetDir)}
|
|
|
496
518
|
inThink = false;
|
|
497
519
|
// 思考块结束:stdout 模式下换行 + 重置样式
|
|
498
520
|
if (!hasExternalHandler && thinkHeaderPrinted) {
|
|
499
|
-
process.stdout.write(
|
|
521
|
+
process.stdout.write("\x1b[0m\n");
|
|
500
522
|
thinkHeaderPrinted = false;
|
|
501
523
|
}
|
|
502
524
|
}
|
|
@@ -539,10 +561,10 @@ ${(0, getSystemPromptTemplate_1.getSystemPromptTemplate)(this.targetDir)}
|
|
|
539
561
|
// 收尾:stdout 模式下关闭样式和换行
|
|
540
562
|
if (!hasExternalHandler) {
|
|
541
563
|
if (thinkHeaderPrinted) {
|
|
542
|
-
process.stdout.write(
|
|
564
|
+
process.stdout.write("\x1b[0m\n");
|
|
543
565
|
}
|
|
544
566
|
if (aiHeaderPrinted)
|
|
545
|
-
process.stdout.write(
|
|
567
|
+
process.stdout.write("\n");
|
|
546
568
|
}
|
|
547
569
|
const aiMsg = result.generations[0].message;
|
|
548
570
|
const meta = aiMsg.response_metadata || {};
|
|
@@ -591,57 +613,49 @@ ${(0, getSystemPromptTemplate_1.getSystemPromptTemplate)(this.targetDir)}
|
|
|
591
613
|
const totalTokens = state.tokenUsage?.total || 0;
|
|
592
614
|
const totalMs = state.totalDuration || 0;
|
|
593
615
|
if (totalTokens > 0 || totalMs > 0) {
|
|
594
|
-
console.log(
|
|
616
|
+
console.log("\n" + "═".repeat(50));
|
|
595
617
|
console.log(`🏁 \x1b[32;1m[审计任务全量结算]\x1b[0m`);
|
|
596
618
|
console.log(` - 累计消耗总额: \x1b[33m${totalTokens}\x1b[0m Tokens`);
|
|
597
619
|
console.log(` - 累计执行耗时: \x1b[36m${(totalMs / 1000).toFixed(2)}\x1b[0m s`);
|
|
598
|
-
console.log(
|
|
620
|
+
console.log("═".repeat(50) + "\n");
|
|
599
621
|
}
|
|
600
622
|
}
|
|
601
623
|
async createGraph() {
|
|
602
624
|
const workflow = new langgraph_1.StateGraph(AgentState)
|
|
603
|
-
.addNode(
|
|
604
|
-
.addNode(
|
|
605
|
-
.addNode(
|
|
625
|
+
.addNode("agent", (state) => this.callModel(state))
|
|
626
|
+
.addNode("tools", this.toolNode)
|
|
627
|
+
.addNode("interrupt", (state) => {
|
|
606
628
|
return {
|
|
607
|
-
messages: [
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
**任务还没完成吗?** 没关系,只要您回复“继续”,我马上就回来接着干!或者您有什么新的想法要告诉我?`
|
|
613
|
-
})]
|
|
629
|
+
messages: [
|
|
630
|
+
new messages_1.AIMessage({
|
|
631
|
+
content: `Final Answer:\n💡 ⚠️ 抱歉,当前任务消耗的 Token 已达上限 ${this.options.maxTokens}。为了防止无限循环,我已停止执行。你可以尝试分步骤指令,继续发送指令,直到任务完成。`,
|
|
632
|
+
}),
|
|
633
|
+
],
|
|
614
634
|
};
|
|
615
635
|
})
|
|
616
|
-
.addEdge(langgraph_1.START,
|
|
617
|
-
.addConditionalEdges(
|
|
636
|
+
.addEdge(langgraph_1.START, "agent")
|
|
637
|
+
.addConditionalEdges("agent", (state) => {
|
|
618
638
|
const { messages } = state;
|
|
619
639
|
const lastMsg = messages[messages.length - 1];
|
|
620
|
-
const content = lastMsg.content ||
|
|
640
|
+
const content = lastMsg.content || "";
|
|
621
641
|
// 🛑 新增:全局 Token 熔断保护
|
|
622
642
|
// 如果已消耗 Token 超过了 options 中设置的 maxTokens (假设是总限额)
|
|
623
|
-
if (this.options.maxTokens &&
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
content: `⚠️ 抱歉,当前任务消耗的 Token 已达上限 ${this.options.maxTokens}。为了防止无限循环,我已停止执行。你可以尝试分步骤指令,继续发送指令,直到任务完成。`
|
|
627
|
-
}));
|
|
628
|
-
this.printFinalSummary(state);
|
|
629
|
-
return langgraph_1.END;
|
|
643
|
+
if (this.options.maxTokens &&
|
|
644
|
+
state.tokenUsage.total >= this.options.maxTokens) {
|
|
645
|
+
return "interrupt";
|
|
630
646
|
}
|
|
631
647
|
// 1. 如果 AI 想要调用工具,去 tools 节点
|
|
632
648
|
if (lastMsg.tool_calls && lastMsg.tool_calls.length > 0) {
|
|
633
|
-
return
|
|
649
|
+
return "tools";
|
|
634
650
|
}
|
|
635
|
-
const isFinalAnswer = content.includes(
|
|
651
|
+
const isFinalAnswer = content.includes("Final Answer");
|
|
636
652
|
if (isFinalAnswer) {
|
|
637
653
|
this.printFinalSummary(state);
|
|
638
654
|
return langgraph_1.END;
|
|
639
655
|
}
|
|
640
|
-
if (messages.length > 50) {
|
|
641
|
-
return 'interrupt';
|
|
642
|
-
}
|
|
643
656
|
return langgraph_1.END;
|
|
644
|
-
})
|
|
657
|
+
})
|
|
658
|
+
.addEdge("tools", "agent");
|
|
645
659
|
return workflow.compile({ checkpointer: this.checkpointer });
|
|
646
660
|
}
|
|
647
661
|
}
|
package/lib/tools/builtin.d.ts
CHANGED
package/lib/tools/builtin.js
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createDefaultBuiltinTools = createDefaultBuiltinTools;
|
|
4
4
|
const filesystem_1 = require("./filesystem");
|
|
5
|
-
const get_all_tools_schema_1 = require("./loader/get_all_tools_schema");
|
|
6
5
|
const ts_lsp_1 = require("./ts-lsp");
|
|
6
|
+
const get_all_tools_schema_1 = require("./loader/get_all_tools_schema");
|
|
7
|
+
const batch_run_tools_1 = require("./loader/batch_run_tools");
|
|
7
8
|
function createDefaultBuiltinTools(context) {
|
|
8
9
|
const { options } = context;
|
|
9
10
|
return [
|
|
10
11
|
...(0, ts_lsp_1.getTsLspTools)(options?.targetDir || process.cwd()),
|
|
11
12
|
...(0, filesystem_1.getFilesystemTools)(options?.targetDir || process.cwd()),
|
|
12
13
|
get_all_tools_schema_1.getAllToolsSchema,
|
|
14
|
+
batch_run_tools_1.batchRunTools,
|
|
13
15
|
];
|
|
14
16
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.batchRunTools = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const createTool_1 = require("../../utils/createTool");
|
|
6
|
+
const kit_1 = require("../../utils/kit");
|
|
7
|
+
exports.batchRunTools = (0, createTool_1.createTool)({
|
|
8
|
+
name: 'batch_run_tools',
|
|
9
|
+
// 使用更具指令性的英文描述
|
|
10
|
+
description: `Execute multiple tools in parallel. Use this for independent tasks such as reading multiple files, applying unrelated edits to different files, or fetching diagnostics simultaneously. CRITICAL: DO NOT apply multiple edits to the same file within a single batch to avoid write conflicts.`,
|
|
11
|
+
parameters: zod_1.z.object({
|
|
12
|
+
actions: zod_1.z.array(zod_1.z.object({
|
|
13
|
+
// 字段名和描述对齐 AI 习惯
|
|
14
|
+
tool_name: zod_1.z.string().describe('The name of the tool to execute.'),
|
|
15
|
+
args: zod_1.z.any().describe('The JSON object containing arguments for the specific tool.')
|
|
16
|
+
})).describe('A list of tool-calling actions to be executed concurrently.')
|
|
17
|
+
}),
|
|
18
|
+
handler: async ({ actions }, context) => {
|
|
19
|
+
// 1. 获取所有可用工具的映射
|
|
20
|
+
const toolMap = (0, kit_1.getArray)(context?.allTools).reduce((acc, tool) => ({ ...acc, [tool.function.name]: tool }), {});
|
|
21
|
+
if (Object.keys(toolMap).length === 0) {
|
|
22
|
+
return 'Error: Failed to retrieve tool execution context.';
|
|
23
|
+
}
|
|
24
|
+
// 2. 并行执行所有 Action
|
|
25
|
+
const results = await Promise.all(actions.map(async (action) => {
|
|
26
|
+
const { tool_name, args } = action;
|
|
27
|
+
const tool = toolMap[tool_name];
|
|
28
|
+
if (!tool) {
|
|
29
|
+
return {
|
|
30
|
+
tool_name,
|
|
31
|
+
status: 'error',
|
|
32
|
+
output: `Tool not found: ${tool_name}`
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
// 特别注意:这里调用的是 _handler,请确保参数 args 的结构与子工具 Zod 定义一致
|
|
37
|
+
const output = await tool._handler(args, context);
|
|
38
|
+
return {
|
|
39
|
+
tool_name,
|
|
40
|
+
status: 'success',
|
|
41
|
+
output
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
tool_name,
|
|
47
|
+
status: 'error',
|
|
48
|
+
output: error?.message || String(error)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
// 3. 格式化输出结果
|
|
53
|
+
// 虽然参数改成了英文,但返回给 AI 的结果标题可以保留清晰的结构
|
|
54
|
+
return results.map(res => `### Tool: ${res.tool_name} (${res.status})\n${res.output}\n---`).join('\n');
|
|
55
|
+
}
|
|
56
|
+
});
|
|
@@ -9,25 +9,36 @@ const zod_to_json_schema_1 = __importDefault(require("zod-to-json-schema"));
|
|
|
9
9
|
const createTool_1 = require("../../utils/createTool");
|
|
10
10
|
const generateToolMarkdown_1 = require("../../utils/generateToolMarkdown");
|
|
11
11
|
const kit_1 = require("../../utils/kit");
|
|
12
|
+
const getSystemPromptTemplate_1 = require("../../utils/getSystemPromptTemplate");
|
|
12
13
|
exports.getAllToolsSchema = (0, createTool_1.createTool)({
|
|
13
|
-
name:
|
|
14
|
+
name: "get_all_tools_schema",
|
|
14
15
|
description: 'Use this tool when you encounter a "tool not found" error or are unsure about the parameter schema. It retrieves the full definitions and JSON schemas for all available tools in the current environment.',
|
|
15
16
|
parameters: zod_1.z.object({
|
|
16
|
-
toolName: zod_1.z
|
|
17
|
+
toolName: zod_1.z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Optional: Specify a tool name to get its detailed schema. If omitted, returns all available tools."),
|
|
17
21
|
}),
|
|
18
22
|
// 增加第二个参数 context
|
|
19
23
|
handler: async ({ toolName }, context) => {
|
|
20
24
|
// 这里的 context.allTools 是在运行时从 Agent 实例传入的
|
|
21
25
|
const availableTools = (0, kit_1.getArray)(context?.allTools);
|
|
22
26
|
const targetTools = toolName
|
|
23
|
-
? availableTools.filter(t => t.function.name === toolName)
|
|
27
|
+
? availableTools.filter((t) => t.function.name === toolName)
|
|
24
28
|
: availableTools;
|
|
25
|
-
|
|
29
|
+
let remainPrompt = "";
|
|
30
|
+
if (context?.agentOptions) {
|
|
31
|
+
// 如果AI忘记了工具用法,说明出现了记忆模糊,这里把系统提示词再补充上,加强记忆
|
|
32
|
+
const systemPrompt = (0, getSystemPromptTemplate_1.getSystemPromptTemplate)(context.agentOptions.targetDir);
|
|
33
|
+
remainPrompt = `${systemPrompt || ""}\n${context?.agentOptions?.extraSystemPrompt || ""}`;
|
|
34
|
+
}
|
|
35
|
+
const toolsMarkdown = (0, generateToolMarkdown_1.generateToolMarkdown)(targetTools.map((item) => ({
|
|
26
36
|
...item,
|
|
27
37
|
function: {
|
|
28
38
|
...item.function,
|
|
29
|
-
parameters: (0, zod_to_json_schema_1.default)(item.function.parameters)
|
|
30
|
-
}
|
|
39
|
+
parameters: (0, zod_to_json_schema_1.default)(item.function.parameters),
|
|
40
|
+
},
|
|
31
41
|
})));
|
|
32
|
-
|
|
42
|
+
return `${remainPrompt}\n${toolsMarkdown}`;
|
|
43
|
+
},
|
|
33
44
|
});
|
package/lib/types/type.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import { Client } from
|
|
3
|
-
import { AgentGraphModel } from
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index";
|
|
3
|
+
import { AgentGraphModel } from "../model/AgentGraphModel";
|
|
4
4
|
export interface ApiConfig {
|
|
5
5
|
baseURL: string;
|
|
6
6
|
apiKey: string;
|
|
7
7
|
model: string;
|
|
8
8
|
}
|
|
9
9
|
export interface ToolInfo {
|
|
10
|
-
type:
|
|
10
|
+
type: "function";
|
|
11
11
|
function: {
|
|
12
12
|
name: string;
|
|
13
13
|
description?: string;
|
|
@@ -15,6 +15,7 @@ export interface ToolInfo {
|
|
|
15
15
|
};
|
|
16
16
|
_handler?: (args: any, context: {
|
|
17
17
|
allTools: ToolInfo[];
|
|
18
|
+
agentOptions?: GraphAgentOptions;
|
|
18
19
|
}) => Promise<any>;
|
|
19
20
|
_client?: Client;
|
|
20
21
|
_originalName?: string;
|
|
@@ -42,6 +43,7 @@ export interface GraphAgentOptions<T extends AgentGraphModel = any> extends Agen
|
|
|
42
43
|
recursionLimit?: number;
|
|
43
44
|
/** 是否启用流式输出,默认 false */
|
|
44
45
|
stream?: boolean;
|
|
46
|
+
filterTools?: (tool: ToolInfo) => boolean;
|
|
45
47
|
}
|
|
46
48
|
export interface CreateAgentOptions {
|
|
47
49
|
apiKey: string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { DynamicStructuredTool } from '@langchain/core/tools';
|
|
2
|
-
import { ToolInfo } from '../types/type';
|
|
2
|
+
import { GraphAgentOptions, ToolInfo } from '../types/type';
|
|
3
3
|
export declare function convertToLangChainTool(info: ToolInfo, context: {
|
|
4
4
|
allTools: ToolInfo[];
|
|
5
|
+
agentOptions: GraphAgentOptions;
|
|
5
6
|
}): DynamicStructuredTool<import("zod").ZodObject<any, import("zod").UnknownKeysParam, import("zod").ZodTypeAny, {
|
|
6
7
|
[x: string]: any;
|
|
7
8
|
}, {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import { ToolInfo } from
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { GraphAgentOptions, ToolInfo } from "../types/type";
|
|
3
3
|
export interface CreateToolOptions {
|
|
4
4
|
name: string;
|
|
5
5
|
description: string;
|
|
@@ -9,6 +9,7 @@ export interface CreateToolOptions {
|
|
|
9
9
|
parameters: z.ZodObject<any>;
|
|
10
10
|
handler: (args: any, context: {
|
|
11
11
|
allTools: ToolInfo[];
|
|
12
|
+
agentOptions?: GraphAgentOptions;
|
|
12
13
|
}) => Promise<string>;
|
|
13
14
|
}
|
|
14
15
|
export declare function createTool(options: CreateToolOptions): ToolInfo;
|
package/lib/utils/createTool.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createTool = createTool;
|
|
4
4
|
function createTool(options) {
|
|
5
5
|
return {
|
|
6
|
-
type:
|
|
6
|
+
type: "function",
|
|
7
7
|
function: {
|
|
8
8
|
name: options.name,
|
|
9
9
|
description: options.description,
|
|
@@ -12,7 +12,7 @@ function createTool(options) {
|
|
|
12
12
|
_handler: async (input, context) => {
|
|
13
13
|
// 兼容处理:如果 input 是字符串,尝试解析为 JSON 对象
|
|
14
14
|
let args = input;
|
|
15
|
-
if (typeof input ===
|
|
15
|
+
if (typeof input === "string") {
|
|
16
16
|
try {
|
|
17
17
|
args = JSON.parse(input);
|
|
18
18
|
}
|
|
@@ -6,18 +6,42 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.formatSchema = formatSchema;
|
|
7
7
|
const zod_to_json_schema_1 = __importDefault(require("zod-to-json-schema"));
|
|
8
8
|
function formatSchema(schema) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const jsonSchema = (0, zod_to_json_schema_1.default)(schema);
|
|
10
|
+
// 递归处理函数
|
|
11
|
+
const processProperties = (properties, required = [], level = 0) => {
|
|
12
|
+
if (!properties || typeof properties !== 'object')
|
|
13
|
+
return '';
|
|
14
|
+
const indent = ' '.repeat(level);
|
|
12
15
|
const lines = [];
|
|
13
|
-
for (const key in
|
|
14
|
-
|
|
16
|
+
for (const key in properties) {
|
|
17
|
+
const prop = properties[key];
|
|
18
|
+
const isRequired = required.includes(key);
|
|
19
|
+
// 1. 基础描述
|
|
20
|
+
let line = `${indent}- ${key}: ${prop.type || 'any'}${isRequired ? ' (required)' : ''}`;
|
|
21
|
+
if (prop.description)
|
|
22
|
+
line += ` - ${prop.description}`;
|
|
23
|
+
lines.push(line);
|
|
24
|
+
// 2. 递归处理数组 (Items)
|
|
25
|
+
if (prop.type === 'array' && prop.items) {
|
|
26
|
+
lines.push(`${indent} Items:`);
|
|
27
|
+
if (prop.items.type === 'object' && prop.items.properties) {
|
|
28
|
+
lines.push(processProperties(prop.items.properties, prop.items.required, level + 2));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
lines.push(`${indent} - Type: ${prop.items.type || 'any'}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// 3. 递归处理嵌套对象 (Object)
|
|
35
|
+
if (prop.type === 'object' && prop.properties) {
|
|
36
|
+
lines.push(processProperties(prop.properties, prop.required, level + 1));
|
|
37
|
+
}
|
|
15
38
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
};
|
|
41
|
+
if (jsonSchema.properties) {
|
|
42
|
+
const result = processProperties(jsonSchema.properties, jsonSchema.required, 2); // 保持你原来的缩进感
|
|
43
|
+
return result || 'No parameters';
|
|
20
44
|
}
|
|
21
45
|
const keys = Object.keys(schema.shape);
|
|
22
|
-
return keys.join(', ');
|
|
46
|
+
return keys.length > 0 ? keys.join(', ') : 'No parameters';
|
|
23
47
|
}
|
|
@@ -7,20 +7,41 @@ const kit_1 = require("./kit");
|
|
|
7
7
|
* 将工具定义从 Zod Schema 转换为极简 Markdown 格式
|
|
8
8
|
* 目的:显著节省 System Prompt 的 Token,同时保持 LLM 理解力
|
|
9
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* 递归解析 JSON Schema 并生成简化的 Markdown
|
|
12
|
+
*/
|
|
13
|
+
function parseSchemaRecursive(schema, indent = '') {
|
|
14
|
+
if (!schema)
|
|
15
|
+
return '';
|
|
16
|
+
let res = '';
|
|
17
|
+
// 1. 处理对象类型 (Object)
|
|
18
|
+
if (schema.type === 'object' && schema.properties) {
|
|
19
|
+
Object.entries(schema.properties).forEach(([key, val]) => {
|
|
20
|
+
const isReq = schema.required?.includes(key);
|
|
21
|
+
const type = val.type || (val.anyOf ? 'any' : 'unknown');
|
|
22
|
+
const desc = val.description ? `: ${val.description}` : '';
|
|
23
|
+
res += `${indent}- \`${key}\` (${type}${isReq ? ', required' : ''})${desc}\n`;
|
|
24
|
+
// 递归处理嵌套对象或数组
|
|
25
|
+
res += parseSchemaRecursive(val, indent + ' ');
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
// 2. 处理数组类型 (Array)
|
|
29
|
+
else if (schema.type === 'array' && schema.items) {
|
|
30
|
+
// 标注数组项的结构
|
|
31
|
+
res += `${indent} *Items:* \n${parseSchemaRecursive(schema.items, indent + ' ')}`;
|
|
32
|
+
}
|
|
33
|
+
return res;
|
|
34
|
+
}
|
|
10
35
|
function generateToolMarkdown(tools) {
|
|
11
36
|
let markdown = "## Tool Definitions\n\n";
|
|
12
37
|
(0, kit_1.getArray)(tools).forEach((tool) => {
|
|
38
|
+
// 假设 cleanToolDefinition 返回的是符合 JSON Schema 规范的对象
|
|
13
39
|
const { name, description, parameters } = (0, cleanToolDefinition_1.cleanToolDefinition)(tool);
|
|
14
40
|
markdown += `- **${name}**: ${description}\n`;
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const isRequired = requiredFields.includes(paramName);
|
|
20
|
-
const type = schema.type.replace('Zod', '').toLowerCase();
|
|
21
|
-
const desc = schema.description || schema.title || '';
|
|
22
|
-
markdown += ` - \`${paramName}\` (${type}${isRequired ? ', required' : ''})${desc ? `: ${desc}` : ''}\n`;
|
|
23
|
-
});
|
|
41
|
+
// 从根部开始递归解析参数
|
|
42
|
+
if (parameters && parameters.type === 'object') {
|
|
43
|
+
markdown += parseSchemaRecursive(parameters, ' ');
|
|
44
|
+
}
|
|
24
45
|
markdown += "\n";
|
|
25
46
|
});
|
|
26
47
|
return markdown;
|
|
@@ -11,6 +11,7 @@ Before providing any output or calling a tool, you **MUST** conduct a deep logic
|
|
|
11
11
|
1. **Pure JSON Arguments**: Arguments must be a valid JSON object. NEVER wrap the entire JSON object in a string or quotes.
|
|
12
12
|
2. **No Double Escaping**: Do not double-escape characters within the JSON.
|
|
13
13
|
3. **No Idle Operations**: If the task is complete or no tool is needed, DO NOT output any "Action" structure. Never use "None", "null", or empty strings as a tool name.
|
|
14
|
+
4. **Important Note**: For optimal efficiency, when performing multiple operations, use batch_run_tools to invoke all relevant tools in parallel, rather than sequentially. Prioritize parallel tool invocation whenever possible. For example, when reading three files, run three tool invocations in parallel to read all three files into the context simultaneously. When running multiple read-only commands (such as read_file_range, grep_search, or read_text_file), always run all commands in parallel. Use parallel tool invocations whenever possible, rather than running too many tools sequentially.
|
|
14
15
|
|
|
15
16
|
# 🎯 Core Instructions
|
|
16
17
|
1. **Termination Criterion**: Once you have read the requested files, answered the questions, or completed the code implementation, you must provide the final response immediately.
|