@saber2pr/ai-agent 0.0.24 → 0.0.25

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.
@@ -46,7 +46,6 @@ const tools_1 = require("@langchain/core/tools");
46
46
  const openai_1 = require("@langchain/openai");
47
47
  const config_1 = require("../config/config");
48
48
  const builtin_1 = require("../tools/builtin");
49
- const jsonSchemaToZod_1 = require("../utils/jsonSchemaToZod");
50
49
  class McpChainAgent {
51
50
  constructor(options) {
52
51
  this.allTools = [];
@@ -125,7 +124,7 @@ class McpChainAgent {
125
124
  const langchainTools = this.allTools.map(t => new tools_1.DynamicStructuredTool({
126
125
  name: t.function.name,
127
126
  description: t.function.description || "",
128
- schema: (0, jsonSchemaToZod_1.jsonSchemaToZod)(t.function.parameters),
127
+ schema: (t.function.parameters),
129
128
  func: async (args) => await t._handler(args),
130
129
  }));
131
130
  const prompt = prompts_1.PromptTemplate.fromTemplate(`{system_prompt}
@@ -1,11 +1,16 @@
1
1
  import { BaseMessage } from "@langchain/core/messages";
2
2
  import { AgentOptions } from "../types/type";
3
3
  export declare const CONFIG_FILE: string;
4
+ interface TokenUsage {
5
+ total: number;
6
+ }
4
7
  declare const AgentState: import("@langchain/langgraph").AnnotationRoot<{
5
8
  messages: import("@langchain/langgraph").BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
6
9
  auditedFiles: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
7
10
  targetCount: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
8
11
  mode: import("@langchain/langgraph").BinaryOperatorAggregate<"chat" | "auto", "chat" | "auto">;
12
+ tokenUsage: import("@langchain/langgraph").BinaryOperatorAggregate<TokenUsage, TokenUsage>;
13
+ totalDuration: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
9
14
  }>;
10
15
  export default class McpGraphAgent {
11
16
  private model;
@@ -15,6 +20,8 @@ export default class McpGraphAgent {
15
20
  private checkpointer;
16
21
  private langchainTools;
17
22
  private stopLoadingFunc;
23
+ private verbose;
24
+ private alwaysSystem;
18
25
  constructor(options?: AgentOptions);
19
26
  private showLoading;
20
27
  private startLoading;
@@ -26,30 +33,44 @@ export default class McpGraphAgent {
26
33
  private renderOutput;
27
34
  callModel(state: typeof AgentState.State): Promise<{
28
35
  messages: unknown[];
36
+ tokenUsage: {
37
+ total: number;
38
+ };
39
+ totalDuration: number;
29
40
  }>;
41
+ private getRecentToolCalls;
30
42
  trackProgress(state: typeof AgentState.State): Promise<{
31
43
  auditedFiles: string[];
32
44
  }>;
45
+ private printFinalSummary;
33
46
  createGraph(): Promise<import("@langchain/langgraph").CompiledStateGraph<import("@langchain/langgraph").StateType<{
34
47
  messages: import("@langchain/langgraph").BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
35
48
  auditedFiles: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
36
49
  targetCount: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
37
50
  mode: import("@langchain/langgraph").BinaryOperatorAggregate<"chat" | "auto", "chat" | "auto">;
51
+ tokenUsage: import("@langchain/langgraph").BinaryOperatorAggregate<TokenUsage, TokenUsage>;
52
+ totalDuration: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
38
53
  }>, import("@langchain/langgraph").UpdateType<{
39
54
  messages: import("@langchain/langgraph").BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
40
55
  auditedFiles: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
41
56
  targetCount: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
42
57
  mode: import("@langchain/langgraph").BinaryOperatorAggregate<"chat" | "auto", "chat" | "auto">;
58
+ tokenUsage: import("@langchain/langgraph").BinaryOperatorAggregate<TokenUsage, TokenUsage>;
59
+ totalDuration: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
43
60
  }>, "tools" | "agent" | "__start__" | "progress", {
44
61
  messages: import("@langchain/langgraph").BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
45
62
  auditedFiles: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
46
63
  targetCount: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
47
64
  mode: import("@langchain/langgraph").BinaryOperatorAggregate<"chat" | "auto", "chat" | "auto">;
65
+ tokenUsage: import("@langchain/langgraph").BinaryOperatorAggregate<TokenUsage, TokenUsage>;
66
+ totalDuration: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
48
67
  }, {
49
68
  messages: import("@langchain/langgraph").BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
50
69
  auditedFiles: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
51
70
  targetCount: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
52
71
  mode: import("@langchain/langgraph").BinaryOperatorAggregate<"chat" | "auto", "chat" | "auto">;
72
+ tokenUsage: import("@langchain/langgraph").BinaryOperatorAggregate<TokenUsage, TokenUsage>;
73
+ totalDuration: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
53
74
  }, import("@langchain/langgraph").StateDefinition>>;
54
75
  }
55
76
  export {};
@@ -13,17 +13,20 @@ const readline_1 = __importDefault(require("readline"));
13
13
  const fs_1 = __importDefault(require("fs"));
14
14
  const path_1 = __importDefault(require("path"));
15
15
  const os_1 = __importDefault(require("os"));
16
+ const events_1 = require("events");
16
17
  const builtin_1 = require("../tools/builtin");
17
18
  const convertToLangChainTool_1 = require("../utils/convertToLangChainTool");
18
19
  exports.CONFIG_FILE = path_1.default.join(os_1.default.homedir(), ".saber2pr-agent.json");
19
- // --- 1. 定义状态 (State) ---
20
+ // 全局设置:修复 AbortSignal 监听器数量警告
21
+ // LangChain 的 HTTP 客户端会创建多个 AbortSignal,需要增加默认限制
22
+ events_1.EventEmitter.defaultMaxListeners = 100;
23
+ // --- 2. 定义状态 (State) ---
20
24
  const AgentState = langgraph_1.Annotation.Root({
21
25
  messages: (0, langgraph_1.Annotation)({
22
26
  reducer: (x, y) => x.concat(y),
23
27
  default: () => [],
24
28
  }),
25
29
  auditedFiles: (0, langgraph_1.Annotation)({
26
- // 确保列表是累加且去重的
27
30
  reducer: (x, y) => Array.from(new Set([...x, ...y])),
28
31
  default: () => [],
29
32
  }),
@@ -35,17 +38,33 @@ const AgentState = langgraph_1.Annotation.Root({
35
38
  reducer: (x, y) => y !== null && y !== void 0 ? y : x,
36
39
  default: () => "chat",
37
40
  }),
41
+ // ✅ Token 累加器
42
+ tokenUsage: (0, langgraph_1.Annotation)({
43
+ reducer: (x, y) => ({
44
+ total: ((x === null || x === void 0 ? void 0 : x.total) || 0) + ((y === null || y === void 0 ? void 0 : y.total) || 0),
45
+ }),
46
+ default: () => ({ total: 0 }),
47
+ }),
48
+ // ✅ 耗时累加器
49
+ totalDuration: (0, langgraph_1.Annotation)({
50
+ reducer: (x, y) => (x || 0) + (y || 0),
51
+ default: () => 0,
52
+ }),
38
53
  });
39
54
  class McpGraphAgent {
40
55
  constructor(options = {}) {
41
56
  this.checkpointer = new langgraph_1.MemorySaver();
42
57
  this.langchainTools = [];
43
- // ✅ 存储清理 loading 的函数
44
58
  this.stopLoadingFunc = null;
45
59
  this.options = options;
60
+ this.verbose = options.verbose || false;
61
+ this.alwaysSystem = options.alwaysSystem || true;
46
62
  this.targetDir = options.targetDir || process.cwd();
47
63
  process.setMaxListeners(100);
48
- // ✅ 全局退出处理:清理动画并恢复光标
64
+ // ✅ 修复 AbortSignal 监听器数量警告
65
+ // LangChain 的 HTTP 客户端会创建多个 AbortSignal,需要增加默认限制
66
+ // 设置 EventEmitter 的默认 maxListeners,这会影响所有事件发射器(包括 AbortSignal)
67
+ events_1.EventEmitter.defaultMaxListeners = 100;
49
68
  const cleanup = () => {
50
69
  this.stopLoading();
51
70
  process.stdout.write('\u001B[?25h');
@@ -53,15 +72,14 @@ class McpGraphAgent {
53
72
  };
54
73
  process.on("SIGINT", cleanup);
55
74
  process.on("SIGTERM", cleanup);
75
+ // ✅ 初始化内置工具
56
76
  const builtinToolInfos = (0, builtin_1.createDefaultBuiltinTools)({ options });
57
77
  this.langchainTools = [...builtinToolInfos, ...(options.tools || [])].map((t) => (0, convertToLangChainTool_1.convertToLangChainTool)(t));
58
78
  this.toolNode = new prebuilt_1.ToolNode(this.langchainTools);
59
79
  }
60
- // ✅ 1. 核心 Loading 动画效果
61
80
  showLoading(text) {
62
81
  const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
63
82
  let i = 0;
64
- // 隐藏光标
65
83
  process.stdout.write('\u001B[?25l');
66
84
  const timer = setInterval(() => {
67
85
  process.stdout.write(`\r\x1b[36m${chars[i]}\x1b[0m ${text}`);
@@ -69,8 +87,8 @@ class McpGraphAgent {
69
87
  }, 80);
70
88
  return () => {
71
89
  clearInterval(timer);
72
- process.stdout.write('\r\x1b[K'); // 清行
73
- process.stdout.write('\u001B[?25h'); // 恢复光标
90
+ process.stdout.write('\r\x1b[K');
91
+ process.stdout.write('\u001B[?25h');
74
92
  };
75
93
  }
76
94
  startLoading(text) {
@@ -111,7 +129,6 @@ class McpGraphAgent {
111
129
  if (!config.baseURL || !config.apiKey) {
112
130
  const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
113
131
  const question = (q) => new Promise((res) => rl.question(q, res));
114
- console.log(`💡 首次运行请配置信息:`);
115
132
  config.baseURL = config.baseURL || await question(`? API Base URL: `);
116
133
  config.apiKey = config.apiKey || await question(`? API Key: `);
117
134
  config.model = config.model || await question(`? Model Name: `) || "gpt-4o";
@@ -126,11 +143,10 @@ class McpGraphAgent {
126
143
  const stream = await app.stream({
127
144
  messages: [new messages_1.HumanMessage(query)],
128
145
  mode: "auto",
129
- targetCount: 4
130
- }, { configurable: { thread_id: "auto_worker" }, recursionLimit: 100 });
146
+ targetCount: 4,
147
+ }, { configurable: { thread_id: "auto_worker" }, recursionLimit: 100, debug: this.verbose });
131
148
  for await (const output of stream)
132
149
  this.renderOutput(output);
133
- console.log("\n✅ 审计任务已完成。");
134
150
  }
135
151
  async start() {
136
152
  await this.getModel();
@@ -142,7 +158,6 @@ class McpGraphAgent {
142
158
  process.stdout.write('\u001B[?25h');
143
159
  process.exit(0);
144
160
  });
145
- console.log(`\n💬 已进入交互审计模式 (Thread: session)`);
146
161
  const ask = () => {
147
162
  rl.question("> ", async (input) => {
148
163
  if (input.toLowerCase() === "exit") {
@@ -158,80 +173,173 @@ class McpGraphAgent {
158
173
  ask();
159
174
  }
160
175
  renderOutput(output) {
161
- var _a, _b;
162
- this.stopLoading(); // 收到输出的第一时间关掉 Loading
163
- // 1. 处理 Agent 节点的输出
176
+ var _a, _b, _c;
177
+ this.stopLoading(); // 停止加载动画
164
178
  const agentNode = output.agent;
179
+ // ✅ 打印工具执行结果(tools 节点的输出)
180
+ const toolsNode = output.tools;
181
+ if (toolsNode && toolsNode.messages) {
182
+ const toolMessages = Array.isArray(toolsNode.messages) ? toolsNode.messages : [];
183
+ // 获取最近的 AI 消息以匹配 tool_call_id
184
+ const lastAiMsg = (_a = agentNode === null || agentNode === void 0 ? void 0 : agentNode.messages) === null || _a === void 0 ? void 0 : _a[agentNode.messages.length - 1];
185
+ const toolCallMap = new Map();
186
+ if (lastAiMsg === null || lastAiMsg === void 0 ? void 0 : lastAiMsg.tool_calls) {
187
+ lastAiMsg.tool_calls.forEach((tc) => {
188
+ if (tc.id)
189
+ toolCallMap.set(tc.id, tc.name);
190
+ });
191
+ }
192
+ toolMessages.forEach((msg) => {
193
+ // ToolMessage 有 tool_call_id 字段
194
+ const toolCallId = msg.tool_call_id || msg.id;
195
+ if (toolCallId) {
196
+ const toolName = toolCallMap.get(toolCallId) || msg.name || 'unknown';
197
+ const content = typeof msg.content === 'string'
198
+ ? msg.content
199
+ : JSON.stringify(msg.content);
200
+ // 如果内容太长,截断显示
201
+ const displayContent = content.length > 500
202
+ ? content.substring(0, 500) + '...'
203
+ : content;
204
+ console.log(`✅ [工具结果] ${toolName}: ${displayContent}`);
205
+ }
206
+ });
207
+ }
165
208
  if (agentNode) {
166
- const msg = agentNode.messages[0];
167
- const reasoning = (_a = msg.additional_kwargs) === null || _a === void 0 ? void 0 : _a.reasoning;
209
+ const msg = agentNode.messages[agentNode.messages.length - 1];
210
+ // 1. 打印思考过程(如果有)
211
+ const reasoning = (_b = msg.additional_kwargs) === null || _b === void 0 ? void 0 : _b.reasoning;
168
212
  if (reasoning) {
169
- console.log("\n🧠 [思考过程]:\n" + "─".repeat(50) + "\n" + reasoning + "\n" + "─".repeat(50));
213
+ console.log(`\n🧠 [思考]: ${reasoning}`);
214
+ }
215
+ // 2. 打印 AI 回复内容
216
+ if (msg.content) {
217
+ console.log(`🤖 [AI]: ${msg.content}`);
218
+ }
219
+ // ✅ 3. 实时打印当次统计信息
220
+ // 这里的 meta 数据是从 AgentGraphModel 的 _generate 中塞进去的
221
+ const meta = msg.response_metadata || {};
222
+ const token = meta.token || 0;
223
+ const duration = meta.duration || 0;
224
+ if (token > 0 || duration > 0) {
225
+ process.stdout.write(`📊 \x1b[2m[实时统计] 消耗: ${token} tokens | 耗时: ${duration}ms\x1b[0m\n`);
170
226
  }
171
- if (msg.content)
172
- console.log("🤖 [AI]:", msg.content);
173
- if ((_b = msg.tool_calls) === null || _b === void 0 ? void 0 : _b.length) {
227
+ // 4. 打印工具调用情况
228
+ if ((_c = msg.tool_calls) === null || _c === void 0 ? void 0 : _c.length) {
174
229
  msg.tool_calls.forEach((call) => {
175
230
  console.log(`🛠️ [调用工具]: ${call.name} 📦 参数: ${JSON.stringify(call.args)}`);
176
231
  });
177
232
  }
178
233
  }
179
- // 2. 处理工具节点的简要反馈(防止大数据量锁死终端)
180
- if (output.tools) {
181
- console.log(`✅ [工具执行完毕]`);
182
- }
183
234
  }
184
235
  async callModel(state) {
185
236
  const auditedListStr = state.auditedFiles.length > 0
186
237
  ? state.auditedFiles.map(f => `\n - ${f}`).join("")
187
238
  : "暂无";
188
- const prompt = prompts_1.ChatPromptTemplate.fromMessages([
189
- ["system", `你是一个代码专家。工作目录:${this.targetDir}。
239
+ const recentToolCalls = this.getRecentToolCalls(state.messages);
240
+ const recentToolCallsStr = recentToolCalls.length > 0
241
+ ? `\n\n⚠️ 最近调用的工具(避免重复调用相同工具和参数):\n${recentToolCalls.map(tc => ` - ${tc.name}(${JSON.stringify(tc.args)})`).join("\n")}`
242
+ : "";
243
+ // 1. 构建当前的系统提示词模板
244
+ const systemPromptTemplate = `你是一个代码专家。工作目录:${this.targetDir}。
190
245
  当前模式:{mode}
191
246
  进度:{doneCount}/{targetCount}
192
247
  已审计文件:{auditedList}
193
248
 
249
+ # 格式要求
250
+ 1. Arguments 必须是纯粹的 JSON 对象,严禁将其作为字符串放入引号中。
251
+ 2. 严禁对 JSON 内容进行二次转义。
252
+
194
253
  # 指令
195
- 1. 优先通过 directory_tree 了解结构。
196
- 2. 发现问题后,先用 apply_fix 修复,再用 generate_review 提交。
197
- 3. 严禁反复执行同一个失败的操作。
198
- {extraPrompt}`],
254
+ 1. 一旦完成对一个文件的修改(apply_fix),请立即调用 generate_review 总结该文件的变动。
255
+ 2. 避免陷入在同一个文件上的无限循环尝试。
256
+ 3. 不要重复调用相同的工具和参数,如果工具已经返回结果,请基于结果继续工作而不是再次调用。{recentToolCalls}
257
+ {extraPrompt}`;
258
+ // 2. 核心逻辑:处理消息上下文
259
+ let inputMessages;
260
+ // ✅ 检查 options 中的 alwaysSystem 参数 (默认为 true 或根据你的需求设置)
261
+ // 如果不希望每次都携带(即只在首轮携带),则过滤掉历史消息里的 SystemMessage
262
+ if (this.options.alwaysSystem === false) {
263
+ inputMessages = state.messages.filter(msg => msg._getType() !== "system");
264
+ }
265
+ else {
266
+ // 默认模式:保持干净,由 PromptTemplate 重新生成最新的 System 状态
267
+ inputMessages = state.messages.filter(msg => msg._getType() !== "system");
268
+ }
269
+ const prompt = prompts_1.ChatPromptTemplate.fromMessages([
270
+ ["system", systemPromptTemplate],
199
271
  new prompts_1.MessagesPlaceholder("messages"),
200
272
  ]);
201
273
  this.startLoading("AI 正在分析并思考中");
202
274
  try {
203
275
  const chain = prompt.pipe(this.model);
204
276
  const response = await chain.invoke({
205
- messages: state.messages,
277
+ messages: inputMessages, // ✅ 使用处理后的消息列表
206
278
  mode: state.mode,
207
279
  targetCount: state.targetCount,
208
280
  doneCount: state.auditedFiles.length,
209
281
  auditedList: auditedListStr,
282
+ recentToolCalls: recentToolCallsStr,
210
283
  extraPrompt: this.options.extraSystemPrompt || "",
211
284
  });
212
285
  this.stopLoading();
213
- return { messages: [response] };
286
+ const meta = response.response_metadata || {};
287
+ const currentToken = Number(meta.token) || 0;
288
+ const currentDuration = Number(meta.duration) || 0;
289
+ return {
290
+ messages: [response],
291
+ tokenUsage: { total: currentToken },
292
+ totalDuration: currentDuration
293
+ };
214
294
  }
215
295
  catch (error) {
216
296
  this.stopLoading();
217
297
  throw error;
218
298
  }
219
299
  }
300
+ getRecentToolCalls(messages, limit = 5) {
301
+ var _a;
302
+ const toolCalls = [];
303
+ // 从后往前遍历消息,收集最近的工具调用
304
+ for (let i = messages.length - 1; i >= 0 && toolCalls.length < limit; i--) {
305
+ const msg = messages[i];
306
+ if ((_a = msg.tool_calls) === null || _a === void 0 ? void 0 : _a.length) {
307
+ for (const tc of msg.tool_calls) {
308
+ toolCalls.push({ name: tc.name, args: tc.args });
309
+ if (toolCalls.length >= limit)
310
+ break;
311
+ }
312
+ }
313
+ }
314
+ return toolCalls;
315
+ }
220
316
  async trackProgress(state) {
221
317
  var _a;
222
318
  const lastAiMsg = state.messages[state.messages.length - 1];
223
- const currentAudited = [...state.auditedFiles];
319
+ const currentAudited = new Set(state.auditedFiles);
224
320
  if ((_a = lastAiMsg === null || lastAiMsg === void 0 ? void 0 : lastAiMsg.tool_calls) === null || _a === void 0 ? void 0 : _a.length) {
225
321
  for (const tc of lastAiMsg.tool_calls) {
226
- // 兼容不同的参数命名习惯
227
- const file = tc.args.path || tc.args.filePath || tc.args.file;
322
+ // 兼容所有可能的路径参数字段
323
+ const file = tc.args.path || tc.args.filePath || tc.args.file || tc.args.file_path;
228
324
  if (file && typeof file === 'string') {
229
- currentAudited.push(file);
325
+ currentAudited.add(file);
230
326
  }
231
327
  }
232
328
  }
233
- // 注意:这里的 reducer 会自动帮我们处理去重
234
- return { auditedFiles: currentAudited };
329
+ return { auditedFiles: Array.from(currentAudited) };
330
+ }
331
+ printFinalSummary(state) {
332
+ var _a;
333
+ const totalTokens = ((_a = state.tokenUsage) === null || _a === void 0 ? void 0 : _a.total) || 0;
334
+ const totalMs = state.totalDuration || 0;
335
+ if (totalTokens > 0 || totalMs > 0) {
336
+ console.log("\n" + "═".repeat(50));
337
+ console.log(`🏁 \x1b[32;1m[审计任务全量结算]\x1b[0m`);
338
+ console.log(` - 累计消耗总额: \x1b[33m${totalTokens}\x1b[0m Tokens`);
339
+ console.log(` - 累计执行耗时: \x1b[36m${(totalMs / 1000).toFixed(2)}\x1b[0m s`);
340
+ console.log(` - 审计文件总数: ${state.auditedFiles.length} 个`);
341
+ console.log("═".repeat(50) + "\n");
342
+ }
235
343
  }
236
344
  async createGraph() {
237
345
  const workflow = new langgraph_1.StateGraph(AgentState)
@@ -240,25 +348,29 @@ class McpGraphAgent {
240
348
  .addNode("progress", (state) => this.trackProgress(state))
241
349
  .addEdge(langgraph_1.START, "agent")
242
350
  .addConditionalEdges("agent", (state) => {
243
- var _a;
244
- const lastMsg = state.messages[state.messages.length - 1];
245
- // 1. 有工具调用,必须去 tools
246
- if ((_a = lastMsg.tool_calls) === null || _a === void 0 ? void 0 : _a.length)
351
+ const messages = state.messages;
352
+ const lastMsg = messages[messages.length - 1];
353
+ const content = lastMsg.content || "";
354
+ // 1. 如果 AI 想要调用工具,去 tools 节点
355
+ if (lastMsg.tool_calls && lastMsg.tool_calls.length > 0) {
247
356
  return "tools";
248
- // 2. 自动模式下,如果审计文件数未达标,继续循环
249
- if (state.mode === "auto") {
250
- if (state.auditedFiles.length < state.targetCount)
251
- return "agent";
252
357
  }
253
- // 3. 默认结束(对话模式或任务已达标)
358
+ // 2. 判定结束的条件:
359
+ // - 模式是 auto 且审计完成
360
+ // - 或者 AI 明确输出了结束语
361
+ // - 或者 AI 输出了普通内容且没有工具调用(针对问答模式)
362
+ const isAutoFinished = state.mode === "auto" && state.auditedFiles.length >= state.targetCount;
363
+ const isFinalAnswer = content.includes("Final Answer");
364
+ // ✅ 修复核心:如果 AI 只是在聊天(没有工具调用),直接结束,不要跳回 agent
365
+ if (isAutoFinished || isFinalAnswer || state.mode === "chat") {
366
+ this.printFinalSummary(state);
367
+ return langgraph_1.END;
368
+ }
369
+ // 兜底:如果是在 auto 模式且还没干完活,才跳回 agent(通常不会走到这里)
254
370
  return langgraph_1.END;
255
- }, {
256
- tools: "tools",
257
- agent: "agent",
258
- [langgraph_1.END]: langgraph_1.END,
259
371
  })
260
372
  .addEdge("tools", "progress")
261
- .addEdge("progress", "agent"); // 闭环回到 agent 进行下一轮决策
373
+ .addEdge("progress", "agent");
262
374
  return workflow.compile({ checkpointer: this.checkpointer });
263
375
  }
264
376
  }
package/lib/core/agent.js CHANGED
@@ -46,6 +46,7 @@ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
46
46
  const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
47
47
  const config_1 = require("../config/config");
48
48
  const builtin_1 = require("../tools/builtin");
49
+ const jsonSchemaToZod_1 = require("../utils/jsonSchemaToZod");
49
50
  class McpAgent {
50
51
  constructor(options) {
51
52
  this.modelName = "";
@@ -202,7 +203,7 @@ class McpAgent {
202
203
  function: {
203
204
  name: `${name}__${t.name}`,
204
205
  description: t.description,
205
- parameters: t.inputSchema,
206
+ parameters: (0, jsonSchemaToZod_1.jsonSchemaToZod)(t.inputSchema),
206
207
  },
207
208
  _originalName: t.name,
208
209
  _client: client,
@@ -4,18 +4,16 @@ import { ChatResult } from '@langchain/core/outputs';
4
4
  export interface AgentGraphLLMResponse {
5
5
  text: string;
6
6
  reasoning?: string;
7
- chatId?: string;
7
+ token?: number;
8
+ duration?: number;
8
9
  }
9
10
  export declare abstract class AgentGraphModel extends BaseChatModel {
10
11
  protected boundTools?: any[];
11
- protected chatId?: string;
12
- constructor(fields?: BaseChatModelParams & {
13
- chatId?: string;
14
- });
12
+ constructor(fields?: BaseChatModelParams);
15
13
  bindTools(tools: any[]): any;
16
- abstract callApi(prompt: string, chatId?: string): Promise<AgentGraphLLMResponse>;
14
+ abstract callApi(prompt: string): Promise<AgentGraphLLMResponse>;
17
15
  _generate(messages: BaseMessage[]): Promise<ChatResult>;
18
- private serializeMessages;
19
16
  private parseToolCalls;
17
+ private serializeMessages;
20
18
  _llmType(): string;
21
19
  }
@@ -7,7 +7,6 @@ const function_calling_1 = require("@langchain/core/utils/function_calling");
7
7
  class AgentGraphModel extends chat_models_1.BaseChatModel {
8
8
  constructor(fields) {
9
9
  super(fields || {});
10
- this.chatId = fields === null || fields === void 0 ? void 0 : fields.chatId;
11
10
  }
12
11
  bindTools(tools) {
13
12
  this.boundTools = tools.map(t => (0, function_calling_1.convertToOpenAITool)(t));
@@ -15,11 +14,9 @@ class AgentGraphModel extends chat_models_1.BaseChatModel {
15
14
  }
16
15
  async _generate(messages) {
17
16
  const fullPrompt = this.serializeMessages(messages);
18
- const response = await this.callApi(fullPrompt, this.chatId);
19
- if (response.chatId)
20
- this.chatId = response.chatId;
21
- let { text, reasoning } = response;
22
- // ✅ 通用逻辑:解析 <think> 标签
17
+ const response = await this.callApi(fullPrompt);
18
+ let { text, reasoning, token, duration } = response;
19
+ // 1. 处理思考内容
23
20
  if (!reasoning && text.includes("<think>")) {
24
21
  const match = text.match(/<think>([\s\S]*?)<\/think>/);
25
22
  if (match) {
@@ -27,18 +24,69 @@ class AgentGraphModel extends chat_models_1.BaseChatModel {
27
24
  text = text.replace(/<think>[\s\S]*?<\/think>/, "").trim();
28
25
  }
29
26
  }
27
+ // 2. 解析工具调用
30
28
  const toolCalls = this.parseToolCalls(text);
29
+ // AgentGraphModel.ts 的 _generate 方法内
31
30
  return {
32
31
  generations: [{
33
32
  text,
34
33
  message: new messages_1.AIMessage({
35
34
  content: text,
36
35
  tool_calls: toolCalls,
37
- additional_kwargs: { reasoning: reasoning || "" }
36
+ additional_kwargs: {
37
+ reasoning: reasoning || "",
38
+ token: response.token, // 👈 必须
39
+ duration: response.duration, // 👈 必须
40
+ },
41
+ response_metadata: {
42
+ reasoning: reasoning || "",
43
+ token: response.token, // 👈 McpGraphAgent 读取路径
44
+ duration: response.duration,
45
+ }
38
46
  })
39
47
  }]
40
48
  };
41
49
  }
50
+ parseToolCalls(text) {
51
+ const actionMatch = text.match(/Action:\s*(\w+)/);
52
+ const argsMatch = text.match(/Arguments:\s*({[\s\S]*?})(?=\n|$)/);
53
+ if (!actionMatch)
54
+ return [];
55
+ let args = {};
56
+ if (argsMatch) {
57
+ try {
58
+ let raw = argsMatch[1].trim();
59
+ // ✅ 关键修复:递归解析,直到它变成真正的对象
60
+ // 这能处理 "\"{\\\"path\\\":...}\"" 这种套娃字符串
61
+ let safetyDepth = 0;
62
+ while (typeof raw === 'string' && safetyDepth < 5) {
63
+ try {
64
+ const parsed = JSON.parse(raw);
65
+ if (typeof parsed === 'object' && parsed !== null) {
66
+ raw = parsed;
67
+ break;
68
+ }
69
+ raw = parsed; // 如果解析后还是 string,继续剥
70
+ }
71
+ catch {
72
+ break; // 解析不动了,跳出
73
+ }
74
+ safetyDepth++;
75
+ }
76
+ args = raw;
77
+ }
78
+ catch (e) {
79
+ args = {};
80
+ }
81
+ }
82
+ // ✅ 此时返回的 args 必须是 object 类型
83
+ return [{
84
+ name: actionMatch[1],
85
+ args: typeof args === 'object' ? args : {},
86
+ id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
87
+ type: "tool_call",
88
+ }];
89
+ }
42
90
  serializeMessages(messages) {
43
91
  const systemMsg = messages.find(m => m._getType() === 'system');
44
92
  const lastMsg = messages[messages.length - 1];
@@ -58,39 +106,6 @@ ${format(lastMsg)}
58
106
  3. Arguments: {JSON}
59
107
  `.trim();
60
108
  }
61
- parseToolCalls(text) {
62
- const actionMatch = text.match(/Action:\s*(\w+)/);
63
- const argsMatch = text.match(/Arguments:\s*({[\s\S]*})/);
64
- if (!actionMatch)
65
- return [];
66
- let args = {};
67
- if (argsMatch) {
68
- try {
69
- // 强力解析逻辑,处理物理换行
70
- const rawArgs = argsMatch[1].trim().replace(/\n/g, "\\n");
71
- args = JSON.parse(rawArgs);
72
- // 参数映射
73
- const anyArgs = args;
74
- if (anyArgs.file_path && !anyArgs.path)
75
- anyArgs.path = anyArgs.file_path;
76
- if (anyArgs.filePath && !anyArgs.path)
77
- anyArgs.path = anyArgs.filePath;
78
- if (anyArgs.path && !anyArgs.filePath)
79
- anyArgs.filePath = anyArgs.path;
80
- if (anyArgs.file && !anyArgs.filePath)
81
- anyArgs.filePath = anyArgs.file;
82
- }
83
- catch (e) {
84
- console.warn("JSON Parse Error", e);
85
- }
86
- }
87
- return [{
88
- name: actionMatch[1],
89
- args,
90
- id: `call_${Date.now()}`,
91
- type: "tool_call",
92
- }];
93
- }
94
109
  _llmType() { return "agent_graph_model"; }
95
110
  }
96
111
  exports.AgentGraphModel = AgentGraphModel;
@@ -9,13 +9,12 @@ const minimatch_1 = require("minimatch");
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const zod_1 = require("zod");
11
11
  const createTool_1 = require("../../utils/createTool");
12
- const jsonSchemaToZod_1 = require("../../utils/jsonSchemaToZod");
13
12
  const lib_1 = require("./lib");
14
13
  // Schema definitions
15
14
  const ReadTextFileArgsSchema = zod_1.z.object({
16
15
  path: zod_1.z.string(),
17
- tail: zod_1.z.number().optional().describe('If provided, returns only the last N lines of the file'),
18
- head: zod_1.z.number().optional().describe('If provided, returns only the first N lines of the file')
16
+ tail: zod_1.z.number().nullable().describe('If provided, returns only the last N lines of the file'),
17
+ head: zod_1.z.number().nullable().describe('If provided, returns only the first N lines of the file')
19
18
  });
20
19
  const ReadMultipleFilesArgsSchema = zod_1.z.object({
21
20
  paths: zod_1.z
@@ -34,7 +33,7 @@ const EditOperation = zod_1.z.object({
34
33
  const EditFileArgsSchema = zod_1.z.object({
35
34
  path: zod_1.z.string(),
36
35
  edits: zod_1.z.array(EditOperation),
37
- dryRun: zod_1.z.boolean().default(false).describe('Preview changes using git-style diff format')
36
+ dryRun: zod_1.z.boolean().nullable().default(false).describe('Preview changes using git-style diff format')
38
37
  });
39
38
  const CreateDirectoryArgsSchema = zod_1.z.object({
40
39
  path: zod_1.z.string(),
@@ -44,11 +43,11 @@ const ListDirectoryArgsSchema = zod_1.z.object({
44
43
  });
45
44
  const ListDirectoryWithSizesArgsSchema = zod_1.z.object({
46
45
  path: zod_1.z.string(),
47
- sortBy: zod_1.z.enum(['name', 'size']).optional().default('name').describe('Sort entries by name or size'),
46
+ sortBy: zod_1.z.enum(['name', 'size']).nullable().default('name').describe('Sort entries by name or size'),
48
47
  });
49
48
  const DirectoryTreeArgsSchema = zod_1.z.object({
50
49
  path: zod_1.z.string(),
51
- excludePatterns: zod_1.z.array(zod_1.z.string()).optional().default([])
50
+ excludePatterns: zod_1.z.array(zod_1.z.string()).nullable().default([])
52
51
  });
53
52
  const MoveFileArgsSchema = zod_1.z.object({
54
53
  source: zod_1.z.string(),
@@ -57,7 +56,7 @@ const MoveFileArgsSchema = zod_1.z.object({
57
56
  const SearchFilesArgsSchema = zod_1.z.object({
58
57
  path: zod_1.z.string(),
59
58
  pattern: zod_1.z.string(),
60
- excludePatterns: zod_1.z.array(zod_1.z.string()).optional().default([])
59
+ excludePatterns: zod_1.z.array(zod_1.z.string()).nullable().default([])
61
60
  });
62
61
  const GetFileInfoArgsSchema = zod_1.z.object({
63
62
  path: zod_1.z.string(),
@@ -90,8 +89,7 @@ const getFilesystemTools = (targetDir) => {
90
89
  "the contents of a single file. Use the 'head' parameter to read only " +
91
90
  "the first N lines of a file, or the 'tail' parameter to read only " +
92
91
  "the last N lines of a file. Operates on the file as text regardless of extension.",
93
- parameters: (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(ReadTextFileArgsSchema, 'ReadTextFileArgsSchema'),
94
- validateParams: ["path"],
92
+ parameters: ReadTextFileArgsSchema,
95
93
  handler: readTextFileHandler
96
94
  });
97
95
  const readMultipleFilesTool = (0, createTool_1.createTool)({
@@ -101,8 +99,7 @@ const getFilesystemTools = (targetDir) => {
101
99
  "or compare multiple files. Each file's content is returned with its " +
102
100
  "path as a reference. Failed reads for individual files won't stop " +
103
101
  "the entire operation. Only works within allowed directories.",
104
- parameters: (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(ReadMultipleFilesArgsSchema, 'ReadMultipleFilesArgsSchema'),
105
- validateParams: ["paths"],
102
+ parameters: ReadMultipleFilesArgsSchema,
106
103
  handler: async (args) => {
107
104
  const results = await Promise.all(args.paths.map(async (filePath) => {
108
105
  try {
@@ -124,8 +121,7 @@ const getFilesystemTools = (targetDir) => {
124
121
  description: "Create a new file or completely overwrite an existing file with new content. " +
125
122
  "Use with caution as it will overwrite existing files without warning. " +
126
123
  "Handles text content with proper encoding. Only works within allowed directories.",
127
- parameters: (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(WriteFileArgsSchema, 'WriteFileArgsSchema'),
128
- validateParams: ["path", "content"],
124
+ parameters: WriteFileArgsSchema,
129
125
  handler: async (args) => {
130
126
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
131
127
  await (0, lib_1.writeFileContent)(validPath, args.content);
@@ -138,8 +134,7 @@ const getFilesystemTools = (targetDir) => {
138
134
  description: "Make line-based edits to a text file. Each edit replaces exact line sequences " +
139
135
  "with new content. Returns a git-style diff showing the changes made. " +
140
136
  "Only works within allowed directories.",
141
- parameters: (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(EditFileArgsSchema, 'EditFileArgsSchema'),
142
- validateParams: ["path", "edits"],
137
+ parameters: EditFileArgsSchema,
143
138
  handler: async (args) => {
144
139
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
145
140
  const result = await (0, lib_1.applyFileEdits)(validPath, args.edits, args.dryRun);
@@ -152,8 +147,7 @@ const getFilesystemTools = (targetDir) => {
152
147
  "nested directories in one operation. If the directory already exists, " +
153
148
  "this operation will succeed silently. Perfect for setting up directory " +
154
149
  "structures for projects or ensuring required paths exist. Only works within allowed directories.",
155
- parameters: (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(CreateDirectoryArgsSchema, 'CreateDirectoryArgsSchema'),
156
- validateParams: ["path"],
150
+ parameters: CreateDirectoryArgsSchema,
157
151
  handler: async (args) => {
158
152
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
159
153
  await promises_1.default.mkdir(validPath, { recursive: true });
@@ -167,8 +161,7 @@ const getFilesystemTools = (targetDir) => {
167
161
  "Results clearly distinguish between files and directories with [FILE] and [DIR] " +
168
162
  "prefixes. This tool is essential for understanding directory structure and " +
169
163
  "finding specific files within a directory. Only works within allowed directories.",
170
- parameters: (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(ListDirectoryArgsSchema, 'ListDirectoryArgsSchema'),
171
- validateParams: ["path"],
164
+ parameters: ListDirectoryArgsSchema,
172
165
  handler: async (args) => {
173
166
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
174
167
  const entries = await promises_1.default.readdir(validPath, { withFileTypes: true });
@@ -184,8 +177,7 @@ const getFilesystemTools = (targetDir) => {
184
177
  "Results clearly distinguish between files and directories with [FILE] and [DIR] " +
185
178
  "prefixes. This tool is useful for understanding directory structure and " +
186
179
  "finding specific files within a directory. Only works within allowed directories.",
187
- parameters: (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(ListDirectoryWithSizesArgsSchema, 'ListDirectoryWithSizesArgsSchema'),
188
- validateParams: ["path"],
180
+ parameters: ListDirectoryWithSizesArgsSchema,
189
181
  handler: async (args) => {
190
182
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
191
183
  const entries = await promises_1.default.readdir(validPath, { withFileTypes: true });
@@ -239,8 +231,7 @@ const getFilesystemTools = (targetDir) => {
239
231
  "Each entry includes 'name', 'type' (file/directory), and 'children' for directories. " +
240
232
  "Files have no children array, while directories always have a children array (which may be empty). " +
241
233
  "The output is formatted with 2-space indentation for readability. Only works within allowed directories.",
242
- parameters: (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(DirectoryTreeArgsSchema, 'DirectoryTreeArgsSchema'),
243
- validateParams: ["path"],
234
+ parameters: DirectoryTreeArgsSchema,
244
235
  handler: async (args) => {
245
236
  const rootPath = args.path;
246
237
  async function buildTree(currentPath, excludePatterns = []) {
@@ -284,8 +275,7 @@ const getFilesystemTools = (targetDir) => {
284
275
  "and rename them in a single operation. If the destination exists, the " +
285
276
  "operation will fail. Works across different directories and can be used " +
286
277
  "for simple renaming within the same directory. Both source and destination must be within allowed directories.",
287
- parameters: (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(MoveFileArgsSchema, 'MoveFileArgsSchema'),
288
- validateParams: ["source", "destination"],
278
+ parameters: MoveFileArgsSchema,
289
279
  handler: async (args) => {
290
280
  const validSourcePath = await (0, lib_1.validatePath)(targetDir, args.source);
291
281
  const validDestPath = await (0, lib_1.validatePath)(targetDir, args.destination);
@@ -298,8 +288,7 @@ const getFilesystemTools = (targetDir) => {
298
288
  name: "search_files",
299
289
  description: "Search for files matching a specific pattern in a specified path. " +
300
290
  "Returns a list of files that match the pattern. Only works within allowed directories.",
301
- parameters: (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(SearchFilesArgsSchema, 'SearchFilesArgsSchema'),
302
- validateParams: ["path", "pattern"],
291
+ parameters: SearchFilesArgsSchema,
303
292
  handler: async (args) => {
304
293
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
305
294
  const results = await (0, lib_1.searchFilesWithValidation)(targetDir, validPath, args.pattern, [], { excludePatterns: args.excludePatterns });
@@ -311,8 +300,7 @@ const getFilesystemTools = (targetDir) => {
311
300
  name: "get_file_info",
312
301
  description: "Get detailed information about a file, including its size, last modified time, and type. " +
313
302
  "Only works within allowed directories.",
314
- parameters: (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(GetFileInfoArgsSchema, 'GetFileInfoArgsSchema'),
315
- validateParams: ["path"],
303
+ parameters: GetFileInfoArgsSchema,
316
304
  handler: async (args) => {
317
305
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
318
306
  const info = await (0, lib_1.getFileStats)(validPath);
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getTsLspTools = void 0;
4
4
  const ts_context_mcp_1 = require("@saber2pr/ts-context-mcp");
5
5
  const createTool_1 = require("../../utils/createTool");
6
+ const zod_1 = require("zod");
6
7
  const getTsLspTools = (targetDir) => {
7
8
  const engine = new ts_context_mcp_1.PromptEngine(targetDir);
8
9
  return [
9
10
  (0, createTool_1.createTool)({
10
11
  name: "get_repo_map",
11
12
  description: "获取项目全局文件结构及导出清单,用于快速定位代码",
12
- parameters: { type: "object", properties: {} },
13
+ parameters: zod_1.z.object({}),
13
14
  handler: async () => {
14
15
  engine.refresh();
15
16
  return engine.getRepoMap();
@@ -18,15 +19,13 @@ const getTsLspTools = (targetDir) => {
18
19
  (0, createTool_1.createTool)({
19
20
  name: "analyze_deps",
20
21
  description: "分析指定文件的依赖关系,支持 tsconfig 路径别名解析",
21
- parameters: { type: "object", properties: { filePath: { type: "string", description: "文件相对路径" } } },
22
- validateParams: ["filePath"],
22
+ parameters: zod_1.z.object({ filePath: zod_1.z.string().describe("文件相对路径") }),
23
23
  handler: async ({ filePath }) => engine.getDeps(filePath),
24
24
  }),
25
25
  (0, createTool_1.createTool)({
26
26
  name: "read_skeleton",
27
27
  description: "提取文件的结构定义(接口、类、方法签名),忽略具体实现以节省 Token",
28
- parameters: { type: "object", properties: { filePath: { type: "string", description: "文件相对路径" } } },
29
- validateParams: ["filePath"],
28
+ parameters: zod_1.z.object({ filePath: zod_1.z.string().describe("文件相对路径") }),
30
29
  handler: async (args) => {
31
30
  const pathArg = args === null || args === void 0 ? void 0 : args.filePath;
32
31
  if (typeof pathArg !== "string" || pathArg.trim() === "") {
@@ -45,8 +44,7 @@ const getTsLspTools = (targetDir) => {
45
44
  (0, createTool_1.createTool)({
46
45
  name: "get_method_body",
47
46
  description: "获取指定文件内某个方法或函数的完整实现代码",
48
- parameters: { type: "object", properties: { filePath: { type: "string", description: "文件相对路径" }, methodName: { type: "string", description: "方法名或函数名" } } },
49
- validateParams: ["filePath", 'methodName'],
47
+ parameters: zod_1.z.object({ filePath: zod_1.z.string().describe("文件相对路径"), methodName: zod_1.z.string().describe("方法名或函数名") }),
50
48
  handler: async ({ filePath, methodName }) => {
51
49
  return engine.getMethodImplementation(filePath, methodName);
52
50
  },
@@ -1,4 +1,5 @@
1
1
  import { Client } from '@modelcontextprotocol/sdk/client/index';
2
+ import { z } from 'zod';
2
3
  export interface ApiConfig {
3
4
  baseURL: string;
4
5
  apiKey: string;
@@ -9,7 +10,7 @@ export interface ToolInfo {
9
10
  function: {
10
11
  name: string;
11
12
  description?: string;
12
- parameters: any;
13
+ parameters: z.ZodObject<any>;
13
14
  };
14
15
  _handler?: (args: any) => Promise<any>;
15
16
  _client?: Client;
@@ -58,4 +59,8 @@ export interface AgentOptions {
58
59
  * only for graph agent
59
60
  */
60
61
  modelName?: string;
62
+ /**
63
+ * only for graph agent
64
+ */
65
+ alwaysSystem?: boolean;
61
66
  }
@@ -2,12 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.convertToLangChainTool = convertToLangChainTool;
4
4
  const tools_1 = require("@langchain/core/tools");
5
- const jsonSchemaToZod_1 = require("./jsonSchemaToZod");
6
5
  function convertToLangChainTool(info) {
7
6
  return new tools_1.DynamicStructuredTool({
8
7
  name: info.function.name,
9
8
  description: info.function.description || "",
10
- schema: (0, jsonSchemaToZod_1.jsonSchemaToZod)(info.function.parameters),
9
+ schema: info.function.parameters,
11
10
  func: async (args) => {
12
11
  if (info._handler)
13
12
  return await info._handler(args);
@@ -1,9 +1,12 @@
1
1
  import { ToolInfo } from '../types/type';
2
+ import { z } from 'zod';
2
3
  export interface CreateToolOptions {
3
4
  name: string;
4
5
  description: string;
5
- parameters: any;
6
+ /**
7
+ * zod@3.23.8
8
+ */
9
+ parameters: z.ZodObject<any>;
6
10
  handler: (args: any) => Promise<string>;
7
- validateParams?: string[];
8
11
  }
9
12
  export declare function createTool(options: CreateToolOptions): ToolInfo;
@@ -10,7 +10,6 @@ function createTool(options) {
10
10
  parameters: options.parameters,
11
11
  },
12
12
  _handler: async (input) => {
13
- var _a;
14
13
  // 兼容处理:如果 input 是字符串,尝试解析为 JSON 对象
15
14
  let args = input;
16
15
  if (typeof input === 'string') {
@@ -21,15 +20,6 @@ function createTool(options) {
21
20
  args = input;
22
21
  }
23
22
  }
24
- if (((_a = options.validateParams) === null || _a === void 0 ? void 0 : _a.length) > 0) {
25
- for (const arg in args) {
26
- if (options.validateParams.includes(arg)) {
27
- if (typeof args[arg] === 'undefined') {
28
- return `Error: 参数 '${arg}' 缺失`;
29
- }
30
- }
31
- }
32
- }
33
23
  return options.handler(args);
34
24
  },
35
25
  };
@@ -1,12 +1,2 @@
1
1
  import { z } from 'zod';
2
- export declare const zodObjectToJsonSchema: (zodObject: z.ZodObject<any>, name: string) => z.ZodObject<any, z.UnknownKeysParam, z.ZodTypeAny, {
3
- [x: string]: any;
4
- }, {
5
- [x: string]: any;
6
- }> | {
7
- title?: string;
8
- default?: any;
9
- description?: string;
10
- markdownDescription?: string;
11
- };
12
2
  export declare function jsonSchemaToZod(parameters: any): z.ZodObject<any>;
@@ -1,14 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.zodObjectToJsonSchema = void 0;
4
3
  exports.jsonSchemaToZod = jsonSchemaToZod;
5
4
  const zod_1 = require("zod");
6
- const zod_to_json_schema_1 = require("zod-to-json-schema");
7
- const zodObjectToJsonSchema = (zodObject, name) => {
8
- var _a, _b;
9
- return ((_b = (_a = (0, zod_to_json_schema_1.zodToJsonSchema)(zodObject, name)) === null || _a === void 0 ? void 0 : _a.definitions) === null || _b === void 0 ? void 0 : _b[name]) || zodObject;
10
- };
11
- exports.zodObjectToJsonSchema = zodObjectToJsonSchema;
12
5
  function jsonSchemaToZod(parameters) {
13
6
  var _a;
14
7
  if (!parameters || typeof parameters !== 'object') {
@@ -57,8 +50,8 @@ function jsonSchemaToZod(parameters) {
57
50
  let schema = convertProp(prop);
58
51
  // 处理必填项
59
52
  const isRequired = (_a = parameters.required) === null || _a === void 0 ? void 0 : _a.includes(key);
60
- obj[key] = isRequired ? schema : schema.optional();
53
+ obj[key] = isRequired ? schema : schema.nullable();
61
54
  }
62
55
  // 使用 passthrough 或 loose 以增加容错性
63
- return zod_1.z.object(obj).passthrough();
56
+ return zod_1.z.object(obj);
64
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saber2pr/ai-agent",
3
- "version": "0.0.24",
3
+ "version": "0.0.25",
4
4
  "description": "AI Assistant CLI.",
5
5
  "author": "saber2pr",
6
6
  "license": "ISC",
@@ -34,8 +34,7 @@
34
34
  "langchain": "0.3.15",
35
35
  "minimatch": "^10.0.1",
36
36
  "openai": "^6.16.0",
37
- "zod": "3.23.8",
38
- "zod-to-json-schema": "3.23.2"
37
+ "zod": "3.23.8"
39
38
  },
40
39
  "resolutions": {
41
40
  "@langchain/core": "0.3.39"