@saber2pr/ai-agent 0.0.9 → 0.0.11

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/README.md CHANGED
@@ -19,6 +19,14 @@ A high-performance AI Agent toolkit designed for automated code auditing, reposi
19
19
  ## 🛠️ Installation
20
20
 
21
21
  ```bash
22
+ sudo npm i -g @saber2pr/ai-agent
23
+
24
+ # call openapi
25
+ sagent
26
+
27
+ # call third api
28
+ sagent-chain
29
+
22
30
  # Clone the repository
23
31
  git clone https://github.com/saber2pr/ai-agent.git
24
32
  cd ai-agent
@@ -103,7 +111,7 @@ class MyPrivateLLM extends BaseChatModel {
103
111
  | ----------------- | ------------------------------------------------------------------------ |
104
112
  | `generate_review` | Finalizes the process by submitting a structured violation report. |
105
113
  | `get_repo_map` | Generates a high-level map of the project files and exports. |
106
- | `read_full_code` | Reads file content with line numbers for precise auditing. |
114
+ | `read_text_file` | Reads file content with line numbers for precise auditing. |
107
115
  | `read_skeleton` | Extracts class/function signatures without full logic (Token efficient). |
108
116
 
109
117
  ---
package/lib/cli-chain.js CHANGED
@@ -4,6 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const agent_chain_1 = __importDefault(require("./agent-chain"));
8
- const manager = new agent_chain_1.default();
9
- manager.start();
7
+ const agent_chain_1 = __importDefault(require("./core/agent-chain"));
8
+ const agent = new agent_chain_1.default();
9
+ agent.start();
package/lib/cli.js CHANGED
@@ -4,6 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const agent_1 = __importDefault(require("./agent"));
8
- const manager = new agent_1.default();
9
- manager.start();
7
+ const agent_1 = __importDefault(require("./core/agent"));
8
+ const agent = new agent_1.default();
9
+ agent.start();
@@ -0,0 +1 @@
1
+ export declare const CONFIG_FILE: string;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CONFIG_FILE = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const os_1 = __importDefault(require("os"));
9
+ exports.CONFIG_FILE = path_1.default.join(os_1.default.homedir(), ".saber2pr-agent.json");
@@ -0,0 +1,27 @@
1
+ import { AgentOptions } from '../types/type';
2
+ export default class McpChainAgent {
3
+ private allTools;
4
+ private messages;
5
+ private encoder;
6
+ private extraTools;
7
+ private maxTokens;
8
+ private executor?;
9
+ private apiConfig;
10
+ private maxIterations;
11
+ private apiModel?;
12
+ private targetDir;
13
+ private verbose;
14
+ constructor(options?: AgentOptions);
15
+ /**
16
+ * 工具处理器包装逻辑:增加日志打印和 Token 监控
17
+ */
18
+ private wrapHandler;
19
+ private initTools;
20
+ private calculateTokens;
21
+ private pruneMessages;
22
+ init(): Promise<void>;
23
+ chat(input: string): Promise<string>;
24
+ private showLoading;
25
+ start(): Promise<void>;
26
+ private ensureApiConfig;
27
+ }
@@ -37,35 +37,35 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  const fs_1 = __importDefault(require("fs"));
40
- const path_1 = __importDefault(require("path"));
41
- const os_1 = __importDefault(require("os"));
42
- const readline = __importStar(require("readline"));
43
- const ts_context_mcp_1 = require("@saber2pr/ts-context-mcp");
44
40
  const js_tiktoken_1 = require("js-tiktoken");
45
- const openai_1 = require("@langchain/openai");
46
- const tools_1 = require("@langchain/core/tools");
47
41
  const agents_1 = require("langchain/agents");
42
+ const readline = __importStar(require("readline"));
48
43
  const prompts_1 = require("@langchain/core/prompts");
49
- const CONFIG_FILE = path_1.default.join(os_1.default.homedir(), ".saber2pr-agent.json");
44
+ const tools_1 = require("@langchain/core/tools");
45
+ const openai_1 = require("@langchain/openai");
46
+ const config_1 = require("../config/config");
47
+ const jsonSchemaToZod_1 = require("../utils/jsonSchemaToZod");
48
+ const builtin_1 = require("../tools/builtin");
50
49
  class McpChainAgent {
51
50
  constructor(options) {
52
51
  this.allTools = [];
53
52
  this.messages = [];
54
53
  this.encoder = (0, js_tiktoken_1.getEncoding)("cl100k_base");
55
54
  this.extraTools = [];
56
- this.engine = new ts_context_mcp_1.PromptEngine((options === null || options === void 0 ? void 0 : options.targetDir) || process.cwd());
57
55
  this.extraTools = (options === null || options === void 0 ? void 0 : options.tools) || [];
58
56
  this.maxTokens = (options === null || options === void 0 ? void 0 : options.maxTokens) || 100000;
59
57
  this.apiConfig = options === null || options === void 0 ? void 0 : options.apiConfig;
60
58
  this.maxIterations = (options === null || options === void 0 ? void 0 : options.maxIterations) || 20;
61
59
  this.apiModel = options === null || options === void 0 ? void 0 : options.apiModel;
60
+ this.targetDir = (options === null || options === void 0 ? void 0 : options.targetDir) || process.cwd();
61
+ this.verbose = (options === null || options === void 0 ? void 0 : options.verbose) || false;
62
62
  const baseSystemPrompt = `你是一个专业的 AI 代码架构师,具备深度的源码分析与工程化处理能力。
63
63
 
64
64
  ### 核心操作规范:
65
65
  1. **全局扫描(强制首选)**:在开始任何分析任务前,你【必须】首先调用 'get_repo_map'。这是理解项目结构、技术栈及模块关系的唯一来源。
66
66
  2. **循序渐进的分析路径**:
67
67
  - 优先使用 'read_skeleton' 提取接口和函数签名。
68
- - 仅在需要分析具体逻辑或准备修复代码时,才使用 'read_full_code'。
68
+ - 仅在需要分析具体逻辑或准备修复代码时,才使用 'read_text_file'。
69
69
  3. **真实性原则**:所有的代码分析必须基于工具返回的真实内容,严禁虚假猜测。`;
70
70
  this.messages.push({
71
71
  role: "system",
@@ -73,8 +73,7 @@ class McpChainAgent {
73
73
  ? `${baseSystemPrompt}\n\n[额外指令]:\n${JSON.stringify(options.extraSystemPrompt)}`
74
74
  : baseSystemPrompt,
75
75
  });
76
- this.registerBuiltinTools();
77
- this.injectCustomTools();
76
+ this.initTools(options);
78
77
  }
79
78
  /**
80
79
  * 工具处理器包装逻辑:增加日志打印和 Token 监控
@@ -95,72 +94,23 @@ class McpChainAgent {
95
94
  return content;
96
95
  };
97
96
  }
98
- registerBuiltinTools() {
99
- const builtinTools = [
100
- {
101
- type: "function",
102
- function: { name: "get_repo_map", description: "获取项目全局结构图和导出清单", parameters: { type: "object" } },
103
- _handler: this.wrapHandler("get_repo_map", async () => {
104
- this.engine.refresh();
105
- return this.engine.getRepoMap();
106
- }),
107
- },
108
- {
109
- type: "function",
110
- function: {
111
- name: "read_skeleton",
112
- description: "读取代码骨架(接口、类定义等),非常节省 Token",
113
- parameters: { type: "object", properties: { filePath: { type: "string" } } },
114
- },
115
- _handler: this.wrapHandler("read_skeleton", async ({ filePath }) => this.engine.getSkeleton(filePath)),
116
- },
117
- {
118
- type: "function",
119
- function: {
120
- name: "read_full_code",
121
- description: "读取完整源码。注意:仅在需要具体行号或精细逻辑时使用",
122
- parameters: { type: "object", properties: { filePath: { type: "string" } } },
123
- },
124
- _handler: this.wrapHandler("read_full_code", async ({ filePath }) => {
125
- // --- 新增:Token 守卫 ---
126
- const currentTokens = this.calculateTokens();
127
- if (currentTokens > this.maxTokens) {
128
- return `[SYSTEM WARNING]: 当前上下文已达到 ${currentTokens} tokens (上限 ${this.maxTokens})。为了保证系统稳定,已拦截 read_full_code。请立即根据已知信息进行总结或停止阅读更多代码。`;
129
- }
130
- try {
131
- if (typeof filePath !== 'string' || !filePath) {
132
- return "Error: filePath 不能为空";
133
- }
134
- // 拼合绝对路径
135
- const fullPath = path_1.default.resolve(this.engine.getRootDir(), filePath);
136
- // 安全检查:防止 AI 尝试读取项目外的敏感文件
137
- if (!fullPath.startsWith(this.engine.getRootDir())) {
138
- return "Error: 权限拒绝,禁止访问项目目录外的文件。";
139
- }
140
- if (!fs_1.default.existsSync(fullPath)) {
141
- return `Error: 文件不存在: ${filePath}`;
142
- }
143
- const content = fs_1.default.readFileSync(fullPath, "utf-8");
144
- // 加上行号,AI 就能在 generate_review 里给出准确的 line 参数
145
- return content.split('\n')
146
- .map((line, i) => `${i + 1} | ${line}`)
147
- .join('\n');
148
- }
149
- catch (err) {
150
- return `Error: 读取文件失败: ${err.message}`;
151
- }
152
- }),
153
- }
97
+ initTools(options) {
98
+ const allTools = [
99
+ // 注册内置工具
100
+ ...(0, builtin_1.createDefaultBuiltinTools)({
101
+ options: {
102
+ ...options,
103
+ ...this
104
+ }
105
+ }),
106
+ ...this.extraTools
154
107
  ];
155
- this.allTools.push(...builtinTools);
156
- }
157
- injectCustomTools() {
158
- for (const tool of this.extraTools) {
159
- this.allTools.push({
160
- type: "function",
161
- function: { name: tool.name, description: tool.description, parameters: tool.parameters },
162
- _handler: this.wrapHandler(tool.name, tool.handler),
163
- });
108
+ if (allTools === null || allTools === void 0 ? void 0 : allTools.length) {
109
+ this.allTools.push(...allTools.map((t) => ({
110
+ type: t.type,
111
+ function: t.function,
112
+ _handler: this.wrapHandler(t.function.name, t._handler),
113
+ })));
164
114
  }
165
115
  }
166
116
  calculateTokens() {
@@ -196,11 +146,20 @@ class McpChainAgent {
196
146
  streaming: false
197
147
  });
198
148
  }
199
- const langchainTools = this.allTools.map(t => new tools_1.DynamicTool({
200
- name: t.function.name,
201
- description: t.function.description || "",
202
- func: t._handler
203
- }));
149
+ const langchainTools = this.allTools.map(t => {
150
+ return new tools_1.DynamicStructuredTool({
151
+ name: t.function.name,
152
+ description: t.function.description || "",
153
+ // 定义 schema 告诉 LangChain 这是一个对象输入
154
+ // passthrough() 允许接收未在 schema 中显式定义的其他字段
155
+ schema: (0, jsonSchemaToZod_1.jsonSchemaToZod)(t.function.parameters),
156
+ func: async (args) => {
157
+ // 这里的 args 已经被 LangChain 自动解析为对象
158
+ return await t._handler(args);
159
+ },
160
+ });
161
+ });
162
+ // src/core/agent-chain.ts 中的 prompt 修改
204
163
  const prompt = prompts_1.PromptTemplate.fromTemplate(`
205
164
  {system_prompt}
206
165
 
@@ -208,39 +167,32 @@ class McpChainAgent {
208
167
  --------------------
209
168
  {tools}
210
169
 
170
+ 工具名称列表: [{tool_names}]
171
+
211
172
  ### 📝 交互协议格式 (PROTOCOL)
212
173
  --------------------
213
- 为了确保任务成功,你必须【严格】遵守以下 ReAct 交互格式:
174
+ 你必须严格遵守以下回复格式:
214
175
 
215
- 1. **思考与行动阶段 (Thought & Action)**:
216
- Thought: 我需要执行什么操作?
217
- Action: 工具名称 (必须是 [{tool_names}] 之一)
218
- Action Input: 工具的 JSON 参数 (例如: {{"filePath": "src/index.ts"}})
219
-
220
- 【⚠️ 极其重要】:当你输出 "Action Input" 后,必须【立即停止】输出,静静等待 Observation(工具返回结果)。严禁在此阶段输出 "Final Answer"。
221
-
222
- 2. **反馈阶段 (Observation)**:
223
- Observation: 工具返回的真实数据。
224
-
225
- 3. **最终结论阶段 (Final Answer)**:
226
- 当且仅当你已经从工具中获得了足够信息并完成所有审计任务时:
227
- Thought: 我已经完成了所有分析,可以生成最终报告。
228
- Final Answer: 任务总结陈述。
176
+ Thought: 首先,我会[此处用中文简述你的分析思路和下一步目标]。
177
+ \`\`\`json
178
+ {{
179
+ "action": "工具名称",
180
+ "action_input": {{ "参数名": "参数值" }}
181
+ }}
182
+ \`\`\`
229
183
 
230
- ### 🚫 强制禁止行为 (STRICT PROHIBITIONS)
231
- --------------------
232
- - **严禁虚构**:禁止使用 'path/to/file' 等占位符,必须使用 'get_repo_map' 返回的真实路径。
233
- - **严禁冲突**:严禁在同一次回复中同时出现 "Action" 和 "Final Answer"。
234
- - **严禁对话**:不要向用户提问或进行闲聊,你的唯一目标是完成审计并调用 'generate_review'。
184
+ 注意:
185
+ - 严禁直接输出 JSON,必须先写 Thought。
186
+ - Thought 必须包含具体的分析意图,不少于 10 个字。
235
187
 
236
188
  Begin!
237
189
  Question: {input}
238
190
  Thought: {agent_scratchpad}`);
239
- const agent = await (0, agents_1.createReactAgent)({ llm: model, tools: langchainTools, prompt });
191
+ const agent = await (0, agents_1.createStructuredChatAgent)({ llm: model, tools: langchainTools, prompt });
240
192
  this.executor = new agents_1.AgentExecutor({
241
193
  agent,
242
194
  tools: langchainTools,
243
- verbose: false, // 我们已经有了 wrapHandler 日志,关闭原生 verbose 以保持整洁
195
+ verbose: this.verbose, // 我们已经有了 wrapHandler 日志,关闭原生 verbose 以保持整洁
244
196
  handleParsingErrors: true,
245
197
  maxIterations: this.maxIterations
246
198
  });
@@ -261,17 +213,34 @@ Thought: {agent_scratchpad}`);
261
213
  // --- 新增:使用回调函数捕获 Thought ---
262
214
  callbacks: [{
263
215
  handleAgentAction: (action, runId) => {
264
- // 在 ReAct Agent 中,thought 通常包含在 log 字段中,且在 Action 之前
265
216
  if (action.log) {
266
- const thought = action.log.split('Action:')[0].trim();
267
- if (thought) {
268
- console.log(`\n💭 [思考]: ${thought.replace('Thought:', '').trim()}`);
217
+ const log = action.log.trim();
218
+ // 1. 提取 Thought 部分:取 Thought: 之后,直到遇到 ```json 或 { 之前的内容
219
+ const thoughtMatch = log.match(/Thought:\s*([\s\S]*?)(?=(?:```json|\{|Action:|$))/i);
220
+ let thought = "";
221
+ if (thoughtMatch && thoughtMatch[1]) {
222
+ thought = thoughtMatch[1].trim();
223
+ }
224
+ else {
225
+ // 备选方案:如果没有 Thought 标签,直接截取 JSON 之前的文本
226
+ thought = log.split(/```json|\{/)[0].replace(/Thought:/i, "").trim();
227
+ }
228
+ // 2. 只有当 thought 真的有文字内容(且不是 JSON)时才打印
229
+ if (thought && thought.length > 0 && !thought.startsWith('{')) {
230
+ // 进一步清洗:如果 thought 包含多行,只取非空的第一行,避免打印太长
231
+ const displayThought = thought.split('\n').find(line => line.trim().length > 0);
232
+ if (displayThought) {
233
+ console.log(`\n💭 [思考]: ${displayThought}`);
234
+ }
269
235
  }
270
236
  }
271
237
  }
272
238
  }]
273
239
  });
274
- let output = response.output;
240
+ // 修复点:确保 output 是字符串
241
+ let output = typeof response.output === 'string'
242
+ ? response.output
243
+ : JSON.stringify(response.output);
275
244
  // 清洗 ReAct 冗余标签
276
245
  if (output.includes("Final Answer:")) {
277
246
  output = ((_a = output.split("Final Answer:").pop()) === null || _a === void 0 ? void 0 : _a.trim()) || output;
@@ -299,7 +268,7 @@ Thought: {agent_scratchpad}`);
299
268
  await this.init();
300
269
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
301
270
  console.log(`\n🚀 AI 助手启动 (LangChain 核心)`);
302
- console.log(`📂 目标目录: ${this.engine.getRootDir()}`);
271
+ console.log(`📂 目标目录: ${this.targetDir}`);
303
272
  const chatLoop = () => {
304
273
  rl.question("\n👤 你: ", async (input) => {
305
274
  if (!input.trim())
@@ -321,8 +290,8 @@ Thought: {agent_scratchpad}`);
321
290
  async ensureApiConfig() {
322
291
  if (this.apiConfig)
323
292
  return this.apiConfig;
324
- if (fs_1.default.existsSync(CONFIG_FILE)) {
325
- return JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, "utf-8"));
293
+ if (fs_1.default.existsSync(config_1.CONFIG_FILE)) {
294
+ return JSON.parse(fs_1.default.readFileSync(config_1.CONFIG_FILE, "utf-8"));
326
295
  }
327
296
  const rl = readline.createInterface({
328
297
  input: process.stdin,
@@ -335,7 +304,7 @@ Thought: {agent_scratchpad}`);
335
304
  apiKey: await question("? API Key: "),
336
305
  model: await question("? Model Name (如 gpt-4o): "),
337
306
  };
338
- fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
307
+ fs_1.default.writeFileSync(config_1.CONFIG_FILE, JSON.stringify(config, null, 2));
339
308
  rl.close();
340
309
  return config;
341
310
  }
@@ -1,44 +1,23 @@
1
- interface ApiConfig {
2
- baseURL: string;
3
- apiKey: string;
4
- model: string;
5
- }
6
- export interface CustomTool {
7
- name: string;
8
- description: string;
9
- parameters: any;
10
- handler: (args: any) => Promise<any>;
11
- }
12
- export interface AgentOptions {
13
- targetDir?: string;
14
- /** 自定义工具扩展 */
15
- tools?: any[];
16
- /** 注入到 System Prompt 中的额外指令/规则/上下文 */
17
- extraSystemPrompt?: any;
18
- maxTokens?: number;
19
- apiConfig?: ApiConfig;
20
- }
1
+ import { AgentOptions } from '../types/type';
21
2
  export default class McpAgent {
22
3
  private openai;
23
4
  private modelName;
24
- private clients;
25
5
  private allTools;
26
6
  private messages;
27
- private engine;
28
7
  private encoder;
29
8
  private extraTools;
30
9
  private maxTokens;
31
10
  private apiConfig;
11
+ private targetDir;
32
12
  constructor(options?: AgentOptions);
33
13
  /**
34
14
  * 计算当前消息列表的总 Token 消耗
35
15
  * 兼容多模态内容 (Content Parts) 和 工具调用 (Tool Calls)
36
16
  */
37
17
  private calculateTokens;
38
- private injectCustomTools;
18
+ private initTools;
39
19
  /**
40
- * 核心功能:内置代码分析工具
41
- * 这里的逻辑直接调用 PromptEngine,不走网络请求,效率极高
20
+ * 核心功能:内置代码分析工具(基于 engine/targetDir,可被外部通过 createDefaultBuiltinTools 替代)
42
21
  */
43
22
  private registerBuiltinTools;
44
23
  private ensureApiConfig;
@@ -62,4 +41,3 @@ export default class McpAgent {
62
41
  chat(input: string): Promise<string>;
63
42
  start(): Promise<void>;
64
43
  }
65
- export {};