@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.
@@ -1,6 +1,6 @@
1
- import { AIMessage, BaseMessage } from '@langchain/core/messages';
2
- import { GraphAgentOptions } from '../types/type';
3
- import { AgentGraphModel } from '../model/AgentGraphModel';
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: 'think' | 'text') => void): void;
33
+ setStreamOutput(callback: (chunk: string, type: "think" | "text") => void): void;
34
34
  /**
35
35
  * 清除外部流式输出回调,恢复默认的 stdout 输出。
36
36
  */
@@ -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('\u001B[?25h');
79
+ process.stdout.write("\u001B[?25h");
80
80
  process.exit(0);
81
81
  };
82
- process.on('SIGINT', cleanup);
83
- process.on('SIGTERM', cleanup);
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('\n🛠️ [Graph] 正在加载工具节点...');
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(), '.cursor', 'mcp.json'),
124
- path_1.default.join(os_1.default.homedir(), '.vscode', 'mcp.json'),
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, 'utf-8'));
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: 'mcp-graph-client', version: '1.0.0' }, { capabilities: {} });
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: 'function',
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)({ options: this.options });
175
+ const builtinToolInfos = (0, builtin_1.createDefaultBuiltinTools)({
176
+ options: this.options,
177
+ });
176
178
  const mcpToolInfos = await this.initMcpTools();
177
179
  // 合并内置、手动传入和 MCP 工具
178
- const allToolInfos = [...builtinToolInfos, ...(this.options.tools || []), ...mcpToolInfos];
179
- this.langchainTools = allToolInfos.map(t => (0, convertToLangChainTool_1.convertToLangChainTool)(t, { allTools: allToolInfos }));
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('\u001B[?25l');
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('\r\x1b[K');
226
- process.stdout.write('\u001B[?25h');
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, 'utf-8'));
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({ input: process.stdin, output: process.stdout });
269
- const question = (q) => new Promise(res => rl.question(q, res));
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 = config.model || (await question(`? Model Name: `)) || 'gpt-4o';
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: 'auto_worker' },
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('\n❌ Chat 过程中发生错误:', 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: 'stream_worker' },
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('\n❌ Stream 过程中发生错误:', 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({ input: process.stdin, output: process.stdout });
331
- rl.on('SIGINT', () => {
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('\u001B[?25h');
352
+ process.stdout.write("\u001B[?25h");
335
353
  process.exit(0);
336
354
  });
337
355
  const ask = () => {
338
- rl.question('> ', async (input) => {
339
- if (input.toLowerCase() === 'exit') {
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: 'session' },
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) ? 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 || 'unknown';
376
- const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(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) + '...' : content;
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('\n')}`
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() !== 'system');
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() !== 'system');
454
+ inputMessages = state.messages.filter((msg) => msg._getType() !== "system");
433
455
  }
434
456
  const prompt = prompts_1.ChatPromptTemplate.fromMessages([
435
- ['system', systemPromptTemplate],
436
- new prompts_1.MessagesPlaceholder('messages'),
457
+ ["system", systemPromptTemplate],
458
+ new prompts_1.MessagesPlaceholder("messages"),
437
459
  ]);
438
- this.startLoading('AI 正在分析并思考中');
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 = '<think>';
451
- const THINK_CLOSE = '</think>';
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, 'text');
483
+ this.streamOutputCallback(text, "text");
462
484
  }
463
485
  else {
464
486
  if (!aiHeaderPrinted) {
465
- process.stdout.write('🤖 [AI]: ');
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, 'think');
497
+ this.streamOutputCallback(text, "think");
476
498
  }
477
499
  else {
478
500
  if (!thinkHeaderPrinted) {
479
- process.stdout.write('\x1b[2m🧠 [思考]: ');
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('\x1b[0m\n');
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('\x1b[0m\n');
564
+ process.stdout.write("\x1b[0m\n");
543
565
  }
544
566
  if (aiHeaderPrinted)
545
- process.stdout.write('\n');
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('\n' + ''.repeat(50));
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(''.repeat(50) + '\n');
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('agent', state => this.callModel(state))
604
- .addNode('tools', this.toolNode)
605
- .addNode('interrupt', state => {
625
+ .addNode("agent", (state) => this.callModel(state))
626
+ .addNode("tools", this.toolNode)
627
+ .addNode("interrupt", (state) => {
606
628
  return {
607
- messages: [new messages_1.AIMessage({
608
- content: `Final Answer:\n💡 **嘿,我们已经聊了很多了!**
609
-
610
- 为了保证服务质量,我通常会在 50 步操作后停下来和您确认一下。这样可以避免我产生“幻觉”或者做一些无用功。
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, 'agent')
617
- .addConditionalEdges('agent', state => {
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 && state.tokenUsage.total >= this.options.maxTokens) {
624
- console.warn('⚠️ [警告] 已达到最大 Token 限制,强制结束任务。');
625
- state.messages.push(new messages_1.AIMessage({
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 'tools';
649
+ return "tools";
634
650
  }
635
- const isFinalAnswer = content.includes('Final Answer');
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
- }).addEdge('tools', 'agent');
657
+ })
658
+ .addEdge("tools", "agent");
645
659
  return workflow.compile({ checkpointer: this.checkpointer });
646
660
  }
647
661
  }
@@ -1,4 +1,4 @@
1
- import { AgentOptions, ToolInfo } from '../types/type';
1
+ import { AgentOptions, ToolInfo } from "../types/type";
2
2
  export interface BuiltinToolsContext {
3
3
  options?: AgentOptions;
4
4
  }
@@ -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,2 @@
1
+ import { ToolInfo } from '../../types/type';
2
+ export declare const batchRunTools: ToolInfo;
@@ -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: 'get_all_tools_schema',
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.string().optional().describe('Optional: Specify a tool name to get its detailed schema. If omitted, returns all available tools.')
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
- return (0, generateToolMarkdown_1.generateToolMarkdown)(targetTools.map(item => ({
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
  });
@@ -1,13 +1,13 @@
1
- import { z } from 'zod';
2
- import { Client } from '@modelcontextprotocol/sdk/client/index';
3
- import { AgentGraphModel } from '../model/AgentGraphModel';
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: 'function';
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 'zod';
2
- import { ToolInfo } from '../types/type';
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;
@@ -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: 'function',
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 === 'string') {
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 res = (0, zod_to_json_schema_1.default)(schema);
10
- if (typeof res.properties === 'object') {
11
- const requiredKeys = res.required || [];
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 res.properties) {
14
- lines.push(` - ${key}: ${res.properties[key].type}${requiredKeys.includes(key) ? ' (required)' : ''}`);
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
- if (lines.length > 0) {
17
- return lines.join('\n');
18
- }
19
- return 'No parameters';
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
  }
@@ -1,5 +1 @@
1
- /**
2
- * 将工具定义从 Zod Schema 转换为极简 Markdown 格式
3
- * 目的:显著节省 System Prompt 的 Token,同时保持 LLM 理解力
4
- */
5
1
  export declare function generateToolMarkdown(tools: any[]): string;
@@ -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
- // 提取 Zod 参数
16
- const shape = parameters.properties || {};
17
- const requiredFields = parameters.required || [];
18
- Object.entries(shape).forEach(([paramName, schema]) => {
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saber2pr/ai-agent",
3
- "version": "0.0.64",
3
+ "version": "0.0.66",
4
4
  "description": "AI Assistant CLI.",
5
5
  "author": "saber2pr",
6
6
  "license": "ISC",